From 17a2a7a42d49b09beb6b9c746039af116998be45 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 26 Jul 2017 12:56:34 +0200 Subject: [PATCH] New module 'Advanced CSS/JS Aggregation' --- sites/all/modules/advagg/LICENSE.txt | 339 ++ sites/all/modules/advagg/README.txt | 372 ++ sites/all/modules/advagg/advagg.admin.inc | 705 +++ sites/all/modules/advagg/advagg.admin.js | 30 + sites/all/modules/advagg/advagg.drush.inc | 49 + sites/all/modules/advagg/advagg.info | 11 + sites/all/modules/advagg/advagg.install | 996 +++++ sites/all/modules/advagg/advagg.missing.inc | 351 ++ sites/all/modules/advagg/advagg.module | 3816 +++++++++++++++++ .../advagg_bundler/advagg_bundler.admin.inc | 96 + .../advagg/advagg_bundler/advagg_bundler.info | 12 + .../advagg_bundler/advagg_bundler.install | 52 + .../advagg_bundler/advagg_bundler.module | 379 ++ .../advagg_css_compress.admin.inc | 77 + .../advagg_css_compress.info | 13 + .../advagg_css_compress.install | 91 + .../advagg_css_compress.module | 186 + .../css-compressor-3.x/Makefile | 34 + .../css-compressor-3.x/README.md | 137 + .../css-compressor-3.x/changelog | 22 + .../css-compressor-3.x/docs/CSSCompression.md | 263 ++ .../css-compressor-3.x/docs/Contribute.md | 63 + .../css-compressor-3.x/docs/Options.md | 234 + .../css-compressor-3.x/license.txt | 21 + .../css-compressor-3.x/src/CSSCompression.inc | 438 ++ .../src/helpers/hex2short-colors.json | 39 + .../src/helpers/hex2short-safe.json | 16 + .../src/helpers/long2hex-colors.json | 126 + .../css-compressor-3.x/src/lib/Cleanup.inc | 218 + .../css-compressor-3.x/src/lib/Color.inc | 196 + .../css-compressor-3.x/src/lib/Combine.inc | 188 + .../src/lib/Combine/Aural.inc | 106 + .../src/lib/Combine/Background.inc | 104 + .../src/lib/Combine/Border.inc | 95 + .../src/lib/Combine/BorderOutline.inc | 106 + .../src/lib/Combine/BorderRadius.inc | 263 ++ .../src/lib/Combine/Font.inc | 122 + .../src/lib/Combine/List.inc | 101 + .../src/lib/Combine/MarginPadding.inc | 187 + .../css-compressor-3.x/src/lib/Compress.inc | 209 + .../css-compressor-3.x/src/lib/Control.inc | 233 + .../css-compressor-3.x/src/lib/Exception.inc | 29 + .../css-compressor-3.x/src/lib/Format.inc | 184 + .../src/lib/Individuals.inc | 304 ++ .../css-compressor-3.x/src/lib/Numeric.inc | 100 + .../css-compressor-3.x/src/lib/Option.inc | 130 + .../css-compressor-3.x/src/lib/Organize.inc | 146 + .../css-compressor-3.x/src/lib/Selectors.inc | 242 ++ .../css-compressor-3.x/src/lib/Setup.inc | 289 ++ .../css-compressor-3.x/src/lib/Trim.inc | 210 + .../csstidy/class.csstidy.inc | 1179 +++++ .../csstidy/class.csstidy_ctype.inc | 46 + .../csstidy/class.csstidy_optimise.inc | 1012 +++++ .../csstidy/class.csstidy_print.inc | 416 ++ .../advagg_css_compress/csstidy/data.inc | 528 +++ .../advagg/advagg_css_compress/yui/CSSMin.inc | 576 +++ .../advagg_css_compress/yui/Compressor.inc | 249 ++ .../advagg/advagg_js_cdn/advagg_js_cdn.info | 12 + .../advagg/advagg_js_cdn/advagg_js_cdn.module | 146 + .../advagg_js_compress.admin.inc | 61 + .../advagg_js_compress.info | 12 + .../advagg_js_compress.install | 197 + .../advagg_js_compress.module | 597 +++ .../advagg/advagg_js_compress/jquery.form.js | 660 +++ .../advagg/advagg_js_compress/jsminplus.inc | 2258 ++++++++++ .../advagg/advagg_js_compress/jspacker.inc | 777 ++++ 66 files changed, 21456 insertions(+) create mode 100644 sites/all/modules/advagg/LICENSE.txt create mode 100644 sites/all/modules/advagg/README.txt create mode 100644 sites/all/modules/advagg/advagg.admin.inc create mode 100644 sites/all/modules/advagg/advagg.admin.js create mode 100644 sites/all/modules/advagg/advagg.drush.inc create mode 100644 sites/all/modules/advagg/advagg.info create mode 100644 sites/all/modules/advagg/advagg.install create mode 100644 sites/all/modules/advagg/advagg.missing.inc create mode 100644 sites/all/modules/advagg/advagg.module create mode 100644 sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc create mode 100644 sites/all/modules/advagg/advagg_bundler/advagg_bundler.info create mode 100644 sites/all/modules/advagg/advagg_bundler/advagg_bundler.install create mode 100644 sites/all/modules/advagg/advagg_bundler/advagg_bundler.module create mode 100644 sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info create mode 100644 sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install create mode 100644 sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.module create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/Makefile create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/README.md create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/changelog create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/CSSCompression.md create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Contribute.md create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Options.md create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/license.txt create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/CSSCompression.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-colors.json create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-safe.json create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/long2hex-colors.json create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Cleanup.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Color.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Aural.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Background.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Border.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderOutline.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderRadius.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Font.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/List.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/MarginPadding.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Compress.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Control.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Exception.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Format.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Individuals.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Numeric.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Option.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Organize.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Selectors.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Setup.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Trim.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_ctype.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_optimise.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_print.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/csstidy/data.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc create mode 100644 sites/all/modules/advagg/advagg_css_compress/yui/Compressor.inc create mode 100644 sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info create mode 100644 sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module create mode 100644 sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.admin.inc create mode 100644 sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.info create mode 100644 sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.install create mode 100644 sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.module create mode 100644 sites/all/modules/advagg/advagg_js_compress/jquery.form.js create mode 100644 sites/all/modules/advagg/advagg_js_compress/jsminplus.inc create mode 100644 sites/all/modules/advagg/advagg_js_compress/jspacker.inc diff --git a/sites/all/modules/advagg/LICENSE.txt b/sites/all/modules/advagg/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/sites/all/modules/advagg/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/advagg/README.txt b/sites/all/modules/advagg/README.txt new file mode 100644 index 0000000..b3ef0fb --- /dev/null +++ b/sites/all/modules/advagg/README.txt @@ -0,0 +1,372 @@ + +---------------------------------- +ADVANCED CSS/JS AGGREGATION MODULE +---------------------------------- + + +CONTENTS OF THIS FILE +--------------------- + + * Fast 404 + * Features & benefits + * Configuration + * Notes + * JavaScript Bookmarklet + * Technical Details & Hooks + * Single htaccess rules + * nginx Configuration + + +FAST 404 +-------- + +Assuming that this guide was followed: +http://2bits.com/drupal-planet/reducing-server-resource-utilization-busy-sites-implementing-fast-404s-drupal.html +and your having issues with Advanced CSS/JS Aggregation. Advagg works similar +to imagecache, thus we need to add in an exceptions for the directories that +advagg uses. Replace this if statement + + if (!strpos($_SERVER['QUERY_STRING'], 'imagecache')) { + +with one like this: + + if (!strpos($_SERVER['QUERY_STRING'], 'imagecache') && !strpos($_SERVER['QUERY_STRING'], '/advagg_')) { + +This will most likely be in your settings.php file. + +If using Nginx make sure there is a rule similar to this in your configuration. +http://drupal.org/node/1116618#comment-4321724 + +If this is still an issue you can try setting the +"IP Address to send all asynchronous requests to" setting on the +admin/settings/advagg/config page to -1. This will use the hostname instead of +an IP address when making the http request. + +If you are still having problems, open an issue on the advagg issue queue: +http://drupal.org/project/issues/advagg + + +FEATURES & BENEFITS +------------------- + +Advanced CSS/JS Aggregation Core Module: + * Imagecache style CSS/JS Aggregation. If the file doesn't exist it will be + generated on demand. + * Stampede protection for CSS and JS aggregation. Uses locking so multiple + requests for the same thing will result in only one thread doing the work. + * Zero file I/O if the Aggregated file already exists. Results in better page + generation performance. + * Fully cached CSS/JS assets making this module faster than drupal core. + * Smarter aggregate deletion. CSS/JS aggregates only get removed from the cache + if they have not been used/accessed in the last 3 days. + * Smarter cache flushing. Scans all CSS/JS files that have been added to any + aggregate; if that file has changed then rebuild all aggregates that contain + the updated file and give the newly aggregated file a new name. The new name + ensures changes go out when using CDNs. + * Works with Drupal's private file system. Can Use a separate directory for + serving aggregated files from. + * Footer JS gets aggregated as well. + * One can add JS to any region & have it aggregated. + drupal_add_js($data, 'module', 'left') is now possible; JS is appended to the + the end of that region. + * One can add external JS/CSS resources. + drupal_add_js('http://example.org/example.js', 'external'); + drupal_add_css('http://example.org/example.css', 'external'); + is now possible. + * Url query string to turn off aggregation for that request. ?advagg=0 will + turn off file aggregation if the user has the "bypass advanced aggregation" + permission. ?advagg=-1 will completely bypass all of Advanced CSS/JS + Aggregations modules and submodules. + * Button on the admin page for dropping a cookie that will turn off file + aggregation. Useful for theme development. + * Url query string to turn on advagg debugging for that request. + ?advagg-debug=1 will output a large debug string to the watchdog if the user + has the "bypass advanced aggregation" permission. + * Gzip support. All aggregated files can be pre-compressed into a .gz file and + served from Apache. This is faster then gzipping the file on each request. + * IE Unlimited CSS support. If using ?advagg=0 the CSS output will change + to use @import style in order to get around the 31 CSS files limit in IE. + * CDN support. Advagg integrates with this module. + * jQuery Update support. Advagg integrates with this module. + * LABjs support. Advagg integrates with this module. + * One year browser cache lifetimes for all aggregated files. This is a good + thing. + * Drush support. "cc advagg" will issue a smart cache flush. + * Admin menu support. Cache flushing Advanced CSS/JS Aggregation is available + in the "Flush all caches" menu. + +Advanced CSS/JS Aggregation Submodules: + CSS: + * CSSTidy library support. Can compress the generated CSS files with the + CSSTidy library. + * CSS Compressor 3.0 support. https://github.com/codenothing/css-compressor + JS: + * JSMin+ library support. Can compress the generated JS files with the jsmin+ + library. + * JSMin PHP Extension support. http://www.ypass.net/software/php_jsmin/ + * Use Dean Edwards Packer on non gzipped files. .gz files will not be packed. + CDN: + * Google's CDN network. Load jquery.js & jquery-ui.js from using the Google + Libraries API. This is a good thing. + Bundler: + * Bundler. Will split up an aggregate into sub aggregates for better load + times throughout your site. + +3rd Party modules: + CSS: + * Parallel CSS - AdvAgg Plugin. Have url()'s in css files reference different + CDN domains. + + +CONFIGURATION +------------- + +Settings page is located at: +admin/settings/advagg + * Enable Advanced Aggregation. You can disable the module here. Same effect as + placing ?advagg=-1 in the URL. + * Use AdvAgg in closure. If enabled javascript files in the closure region will + be aggregated by advagg. + * Generate CSS/JS files on request (async mode). If advagg doesn't have a route + back to its self and this is enabled then you will have a broken site. With + this enabled one can expect much quicker page generation times after a cache + flush. + * Gzip CSS/JS files. For every Aggregated file generated, this will create a + gzip version of that and then serve that out if the browser accepts gzip + compression. + * Generate .htaccess files in the advagg_* dirs. If your using the rules + located at the bottom of this document in your webroots htaccess file then + you can disable this checkbox. + * Regenerate flushed bundles in the cache flush request. You can enable if your + server will not timeout on a request. This will call advagg_rebuild_bundle() + as a shutdown function for every bundle that has been marked as expired; + thus rebuilding that bundle in the same request as the flush. + * Use a different directory for storing advagg files. Only available if your + using a private file system. Allows you to save the generated aggregated + files in a different directory. This gets around the private file system + restrictions. If boost is installed, you can safely use the cache directory. + * Aggregation Inclusion Mode. Should the page wait for the aggregate to be + built before including the file, or should it send out the page with + aggregates not included. + * Disable page caching if all aggregates are not included on the page. + * File Checksum Mode. mtime is the file modification time. md5 is a hash of the + files contents. + * IP Address to send all asynchronous requests to. If you wish to have one + server generate all CSS/JS aggregated files then this allows for that to + happen. + * Smart cache flush button. Scan all files referenced in aggregated files. If any of + them have changed, increment the counters containing that file and rebuild + the bundle. + * Cache Rebuild button. Recreate all aggregated files. Useful if JS or CSS + compression was just enabled. + * Forced Cache Rebuild. Recreate all aggregated files by incrementing internal + counter for every bundle. One should never have to use this option. + * Master Reset. Clean slate; same affect as uninstalling the module. + * Rebuild htaccess files. Recreate the generated htaccess files. + * Aggregation Bypass Cookie. This will set or remove a cookie that disables + aggregation for the remainder of the browser session. It acts almost the same + as adding ?advagg=0 to every URL. + +Additional information is available at: +admin/settings/advagg/info + * Hook Theme Info. Displays the preprocess_page order. Used for debugging. + * CSS files. Displays how often a files checksum has changed and any data + stored about it. + * JS files. Displays how often a files checksum has changed and any data + stored about it. + * Modules implementing advagg hooks. Lets you know what modules are using + advagg. + * Missing files. Lets you know the files that are trying to be added but are + not there. + * Asynchronous debug info. Outputs the the full object returned from + drupal_http_request() which is helpful when debugging async issues. + + +NOTES +----- + +--Bundler Sub Module-- +When using the bundler sub module, tools like Google Page Speed and YSlow will +complain that not all CSS/JS files are in one aggregate. This has to do with +how drupal_add_js/drupal_add_css works. You can get a better score on these +tools by placing all files into one aggregate, but the issue with doing that +is, different pages load different css/js files, thus on page 2 of the users +visit, you will get worse performance because the browser has to re-download a +whole new css & js aggregate rather then the smaller aggregate that only +changed. The bundler attempts to work around this issue by creating various +bundles, each one being chosen fairly smartly so that instead of downloading a +200KB js file you only have to download a 20KB file on the 2nd page. + +The bundler sub module is all about balancing trade offs. You can make a site +that had really good performance stats according to the tools but you would +then have to re-download just about everything on a different page of your +site, because not all your pages are the same. If you don't care about this and +want a good score from pagespeed, disable the bundler sub-module. That will +give you a better score, but then you have to download a new (large) aggregate +on different parts of your website and already downloaded file reuse will be +lower. + +--Cron-- +The cron job for AdvAgg is there as a garbage collector. It only needs to run +once a week; running it every hour isn't going to be bad, it isn't necessary +though. + + +JAVASCRIPT BOOKMARKLET +---------------------- + +You can use this JS code as a bookmarklet for toggling the AdvAgg URL parameter. +See http://en.wikipedia.org/wiki/Bookmarklet for more details. + + javascript:(function(){var loc = document.location.href,qs = document.location.search,regex_off = /\&?advagg=-1/,goto = loc;if(qs.match(regex_off)) {goto = loc.replace(regex_off, '');} else {qs = qs ? qs + '&advagg=-1' : '?advagg=-1';goto = document.location.pathname + qs;}window.location = goto;})(); + + +TECHNICAL DETAILS & HOOKS +------------------------- + +Technical Details: + * There are two database tables and two cache table used by advagg. + * Files are generated by this pattern: css_[MD5]_[Counter].css + * Every JS file is tested for compressibility. This is necessary because jsmin+ + can run out of memory on certain files. This allows us to catch these bad + files and mark them. Also allows us to skip files that are already + compressed. + +Hooks: + * hook_advagg_css_alter. Modify the data before it gets written to the file. + Useful for compression. + * hook_advagg_css_inline_alter. Modify the data before it gets embedded in the + page. Useful for compression. + * hook_advagg_css_pre_alter. Modify the raw $variables['css'] before it gets + processed. Useful for file replacement. + * hook_advagg_css_extra_alter. Allows one to set the a prefix and suffix to be + added into the HTML DOM. Useful for CSS conditionals. + + * hook_advagg_js_alter. Modify the data before it gets written to the file. + Useful for compression. + * hook_advagg_js_inline_alter. Modify the data before it gets embedded in the + page. Useful for compression. + * hook_advagg_js_pre_alter. Modify the raw $javascript before it gets + processed. Useful for file replacement. + * hook_advagg_js_extra_alter. Allows one to set the a prefix and suffix to be + added into the HTML DOM. + * hook_advagg_js_header_footer_alter. Allows one to move JS from the header to + the footer. Also one can look at both header and footer JS arrays before they + get processed. + + * hook_advagg_filenames_alter. Allows for a one to many relationship. A single + request for a bundle name can result in multiple bundles being returned. + * hook_advagg_files_table. Allows for modules to mark a file as expired. + * advagg_master_reset. Allows other modules to take part in a master reset. + * advagg_disable_processor. Allows one to turn off advagg from a hook. See the + advagg_advagg_disable_processor() function for example usage. + * advagg_disable_page_cache. Allows 3rd party page cache plugins like boost or + varnish to not cache this page. + * advagg_bundler_analysis_alter. Give installed modules a chance to alter the + bundler's analysis array. + +JS/CSS Theme Override: + + $conf['advagg_css_render_function'] - advagg_unlimited_css_builder + $conf['advagg_js_render_function'] - advagg_js_builder + +JS/CSS File Save Override: + + $conf['advagg_file_save_function'] - advagg_file_saver + +Public Functions: + * advagg_add_css_inline. Adds the ability to add in inline CSS to the page with + a prefix and suffix being set as well. + + +SINGLE HTACCESS RULES +--------------------- + +If the directory level htaccess rules are interfering with your server, you can +place these rules in the Drupal root htaccess file. Place these rules after +"RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]" but before "" + + + # Rules to correctly serve gzip compressed CSS and JS files. + # Requires both mod_rewrite and mod_headers to be enabled. + + # Serve gzip compressed CSS/JS files if they exist and client accepts gzip. + RewriteCond %{HTTP:Accept-encoding} gzip + RewriteCond %{REQUEST_URI} (^/(.+)/advagg_(j|cs)s/(.+)\.(j|cs)s) [NC] + RewriteCond %{REQUEST_FILENAME}\.gz -s + RewriteRule ^(.*)\.(j|cs)s$ $1\.$2s\.gz [QSA] + + # Serve correct content types, and prevent mod_deflate double gzip. + RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1] + RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1] + + + # Serve correct encoding type. + Header set Content-Encoding gzip + # Force proxies to cache gzipped & non-gzipped css/js files separately. + Header append Vary Accept-Encoding + + + + +You also need to place these rules at the very end of your htaccess file, after +"". + + +# AdvAgg Rules Start. + + # No mod_headers + + # No mod_expires + + # Use ETags. + FileETag MTime Size + + + # Use Expires Directive. + + # Do not use ETags. + FileETag None + # Enable expirations. + ExpiresActive On + # Cache all aggregated CSS/JS files for 480 weeks after access (A). + ExpiresDefault A290304000 + + + + + # Set a far future Cache-Control header to 480 weeks. + Header set Cache-Control "max-age=290304000, no-transform, public" + # Set a far future Expires header. + Header set Expires "Tue, 20 Jan 2037 04:20:42 GMT" + # Pretend the file was last modified a long time ago in the past. + Header set Last-Modified "Wed, 20 Jan 1988 04:20:42 GMT" + # Do not use etags for cache validation. + Header unset ETag + + +# AdvAgg Rules End. + + +Be sure to disable the "Generate .htaccess files in the advagg_* dirs" setting +on the admin/settings/advagg page after placing these rules in the webroots +htaccess file. This is located at the same directory level as Drupal's +index.php. + + +NGINX CONFIGURATION +------------------- + +http://drupal.org/node/1116618 + + ### + ### advagg_css and advagg_js support + ### + location ~* files/advagg_(?:css|js)/ { + access_log off; + expires max; + add_header ETag ""; + add_header Cache-Control "max-age=290304000, no-transform, public"; + add_header Last-Modified "Wed, 20 Jan 1988 04:20:42 GMT"; + try_files $uri @drupal; + } diff --git a/sites/all/modules/advagg/advagg.admin.inc b/sites/all/modules/advagg/advagg.admin.inc new file mode 100644 index 0000000..489d977 --- /dev/null +++ b/sites/all/modules/advagg/advagg.admin.inc @@ -0,0 +1,705 @@ + array('key' => md5(drupal_get_private_key()))), 'setting'); + drupal_add_js($file_path . '/advagg.admin.js'); + + $output .= drupal_get_form('advagg_admin_settings_form'); + return $output; +} + +/** + * Page generation function for admin/settings/advagg/info + */ +function advagg_admin_info_page() { + $output = ''; + return $output . drupal_get_form('advagg_admin_info_form'); +} + +/** + * Form builder; Configure advagg settings. + * + * @ingroup forms + * @see system_settings_form() + */ +function advagg_admin_info_form() { + $form = array(); + + $form['info'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Hook Theme Info'), + ); + init_theme(); + $hooks = theme_get_registry(); + $data = implode("\n", $hooks['page']['preprocess functions']); + $form['info']['advagg_debug_info'] = array( + '#type' => 'textarea', + '#default_value' => $data, + '#rows' => count($hooks['page']['preprocess functions']) + 1, + ); + + $types = db_query("SELECT DISTINCT(filetype) FROM {advagg_files}"); + while ($type = db_result($types)) { + $form[$type] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('@type files', array('@type' => drupal_strtoupper($type))), + ); + // filename filename_md5 checksum filetype counter data + $results = db_query("SELECT * FROM {advagg_files} WHERE filetype = '%s'", $type); + while ($row = db_fetch_array($results)) { + $data = advagg_get_file_data($row['filename_md5']); + if (!empty($data)) { + list($data, $rows) = advagg_form_print_r($data); + + $form[$type][$row['filename_md5']] = array( + '#type' => 'textarea', + '#title' => check_plain($row['filename']), + '#default_value' => $data, + '#rows' => $rows -1, + '#description' => t('File has changed %counter times', array('%counter' => $row['counter'])), + ); + } + else { + $form[$type][$row['filename_md5']] = array( + '#type' => 'markup', + '#value' => '
' . format_plural($row['counter'], 'changed 1 time - %file
', 'changed %counter times - %file
', array( + '%counter' => $row['counter'], + '%file' => $row['filename'], + )), + ); + } + } + } + + // Get hooks in use. + $hooks = array( + 'advagg_css_alter' => array(), + 'advagg_css_inline_alter' => array(), + 'advagg_css_pre_alter' => array(), + 'advagg_css_extra_alter' => array(), + 'advagg_js_alter' => array(), + 'advagg_js_inline_alter' => array(), + 'advagg_js_pre_alter' => array(), + 'advagg_js_extra_alter' => array(), + 'advagg_js_header_footer_alter' => array(), + 'advagg_filenames_alter' => array(), + 'advagg_files_table' => array(), + 'advagg_master_reset' => array(), + 'advagg_disable_processor' => array(), + 'advagg_disable_page_cache' => array(), + 'advagg_bundler_analysis_alter' => array(), + ); + foreach ($hooks as $hook => $values) { + $hooks[$hook] = module_implements($hook); + } + // Record function overrides. Not working currently. +// $hooks['advagg_css_render_function'] = array(variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION)); +// $hooks['advagg_js_render_function'] = array(variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION)); +// $hooks['advagg_file_save_function'] = array(variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION)); + + // Output hooks in form. + foreach ($hooks as $hook => $values) { + if (empty($values)) { + $form['hooks_implemented'][$hook] = array( + '#type' => 'markup', + '#value' => '
' . check_plain($hook) . ':
    ' . t('None') . '
', + ); + } + else { + $form['hooks_implemented'][$hook] = array( + '#type' => 'markup', + '#value' => '
' . check_plain($hook) . ':
    ' . filter_xss(implode('
    ', $values)) . '
', + ); + } + } + $form['hooks_implemented'] += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Modules implementing advagg hooks'), + ); + + // If any files are missing list them. + // TODO: Reset missing files list. + $results = db_query("SELECT * FROM {advagg_files} WHERE checksum = '-1' ORDER BY filetype ASC"); + while ($row = db_fetch_array($results)) { + $form['missing'][$row['filename_md5']] = array( + '#type' => 'markup', + '#value' => '
' . check_plain($row['filename']) . '
', + ); + } + if (!empty($form['missing'])) { + $form['missing'] += array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('Missing files'), + ); + } + + // Asynchronous raw output + $form['async'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Asynchronous debug info'), + ); + + list($css_path, $js_path) = advagg_get_root_files_dir(); + + $filepath = $css_path . '/css_missing' . mt_rand() . time() . '_0.css'; + $url = _advagg_build_url($filepath); + $headers = array( + 'Host' => $_SERVER['HTTP_HOST'], + 'Connection' => 'close', + ); + + timer_start(__FUNCTION__ . 'local'); + $data_local = drupal_http_request($url, $headers); + $data_local->timer = timer_stop(__FUNCTION__ . 'local'); + list($data, $rows) = advagg_form_print_r($data_local); + $form['async']['normal'] = array( + '#type' => 'textarea', + '#title' => check_plain($url), + '#default_value' => $data, + '#rows' => min($rows + 1, 50), + ); + + if (module_exists('cdn')) { + global $conf; + $path_blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT); + $conf[CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE] = ''; + + $url_cdn = advagg_build_uri($filepath); + + $conf[CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE] = $path_blacklist; + // Send request and also time it. + timer_start(__FUNCTION__ . 'cdn'); + $data_cdn = drupal_http_request($url_cdn); + $data_cdn->timer = timer_stop(__FUNCTION__ . 'cdn'); + list($data, $rows) = advagg_form_print_r($data_cdn); + $form['async']['cdn'] = array( + '#type' => 'textarea', + '#title' => check_plain($url_cdn), + '#default_value' => $data, + '#rows' => min($rows + 1, 50), + ); + } + + return $form; +} + +/** + * Form builder; Configure advagg settings. + * + * @ingroup forms + * @see system_settings_form() + */ +function advagg_admin_settings_form() { + $form = array(); + $readme = drupal_get_path('module', 'advagg') . '/README.txt'; + $bundle_count = db_result(db_query("SELECT COUNT(*) FROM (SELECT bundle_md5 FROM {advagg_bundles} GROUP BY bundle_md5) as temp")); + + $form['advagg_enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Advanced Aggregation'), + '#default_value' => variable_get('advagg_enabled', ADVAGG_ENABLED), + ); + $form['advagg_closure'] = array( + '#type' => 'checkbox', + '#title' => t('Use AdvAgg in closure'), + '#default_value' => variable_get('advagg_closure', ADVAGG_CLOSURE), + '#description' => t('If enabled javascript files in the closure region will be aggregated.'), + ); + // Make sure the advagg_check_missing_handler function is available. + module_load_install('advagg'); + $ret = advagg_check_missing_handler(); + $form['advagg_async_generation'] = array( + '#type' => 'checkbox', + '#title' => t('Generate CSS/JS files on request (async mode)'), + '#default_value' => variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION), + '#disabled' => $ret['advagg_async_generation']['severity'] == REQUIREMENT_ERROR ? TRUE : FALSE, + '#description' => t('Current State: !value', array( + '!value' => filter_xss(' ' + . $ret['advagg_async_generation']['value'] . ' ' + . (!empty($ret['advagg_async_generation']['description']) ? $ret['advagg_async_generation']['description'] : '') . ' ' + ), + )), + ); + $form['advagg_gzip_compression'] = array( + '#type' => 'checkbox', + '#title' => t('Gzip CSS/JS files'), + '#default_value' => variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION), + '#description' => t('This might break CSS/JS handling at the Apache level. If it does, use the rules for your webroot level htaccess file before re-enabling. Directions on what to change are located in the readme file. In short, be sure to test this out.', array('@readme' => url($readme))), + ); + $form['advagg_dir_htaccess'] = array( + '#type' => 'checkbox', + '#title' => t('Generate .htaccess files in the advagg_* dirs'), + '#default_value' => variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS), + '#description' => t('Disable if your using the rules from the readme file in your webroot level htaccess file.', array('@readme' => url($readme))), + ); + $form['advagg_rebuild_on_flush'] = array( + '#type' => 'checkbox', + '#title' => t('Regenerate flushed bundles in the cache flush request'), + '#default_value' => variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH), + '#description' => t('You can enable if your server will not timeout on a request. This will call advagg_rebuild_bundle() as a shutdown function for every bundle that has been marked as expired; thus rebuilding that bundle in the same request as the flush.'), + ); + $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + if (!$public_downloads) { + $extra = module_exists('boost') ? t('If boost is installed, you can use the cache directory for advagg files.') : ''; + $form['advagg_custom_files_dir'] = array( + '#type' => 'textfield', + '#field_prefix' => '
' . t('You are using a private file system. You must serve aggregated files via a public folder.') . '
', + '#title' => t('Use a different directory for storing advagg files'), + '#default_value' => variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR), + '#description' => check_plain(t('You need to create a publicly writable directory (same permissions as Drupals files folder).') . ' ' . $extra . ' ' . t('If you do not need this, you can put a space in this box.')), + '#required' => TRUE, + ); + } + $options = array( + 0 => t('Wait for locks'), + 1 => t('Do not wait for locks'), + 2 => t('Only serve aggregated files if they are already built (only works if async is enabled)'), + ); + $form['advagg_aggregate_mode'] = array( + '#type' => 'radios', + '#title' => t('Aggregation Inclusion Mode'), + '#default_value' => variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE), + '#options' => $options, + '#description' => t('Should the page wait for the aggregate to be built before including the file, or should it send out the page with aggregates not included.'), + ); + $form['advagg_page_cache_mode'] = array( + '#type' => 'checkbox', + '#title' => t('Disable page caching if all aggregates are not included on the page.'), + '#default_value' => variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE), + ); + $form['advagg_checksum_mode'] = array( + '#type' => 'radios', + '#title' => t('File Checksum Mode'), + '#default_value' => variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE), + '#options' => array( + 'mtime' => 'mtime', + 'md5' => 'md5', + ), + '#description' => t('If Drupal is on multiple servers and the file system is not shared; using md5 is recommended. The file modification time (mtime) could be different with this type of setup. WARNING: Changing this value will invalidate all bundles and new ones will have to be built.'), + ); + $form['advagg_server_addr'] = array( + '#type' => 'textfield', + '#title' => t('IP Address to send all asynchronous requests to'), + '#default_value' => variable_get('advagg_server_addr', FALSE), + '#description' => t('If left blank it will use the same server as the request. If set to -1 it will use the host name instead of an IP address.'), + ); +// $form['advagg_debug'] = array( +// '#type' => 'checkbox', +// '#title' => t('Debug to watchdog.'), +// '#default_value' => variable_get('advagg_debug', ADVAGG_DEBUG), +// ); + + $form['flush'] = array( + '#type' => 'fieldset', + '#title' => t('Smart cache flush'), + '#description' => t('Scan all files referenced in aggregated files. If any of them have changed, increment the counters containing that file and rebuild the bundle.'), + ); + $form['flush']['advagg_flush'] = array( + '#type' => 'submit', + '#value' => t('Flush AdvAgg Cache'), + '#submit' => array('advagg_admin_flush_cache_button'), + ); + $form['rebuild'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Cache Rebuild'), + '#description' => t('Recreate all aggregated files. Useful if JS or CSS compression was just enabled. %count Files', array('%count' => $bundle_count)), + ); + $form['rebuild']['advagg_rebuild'] = array( + '#type' => 'submit', + '#value' => t('Rebuild AdvAgg Cache'), + '#submit' => array('advagg_admin_batch_rebuild'), + ); + $form['forced_rebuild'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Forced Cache Rebuild'), + '#description' => t('Recreate all aggregated files by incrementing internal counter for every bundle. %count Files', array('%count' => $bundle_count)), + ); + $form['forced_rebuild']['advagg_forced_build'] = array( + '#type' => 'submit', + '#value' => t('Force all counters to be increment by one'), + '#submit' => array('advagg_admin_batch_rebuild'), + ); + $form['master_reset'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Master Reset'), + '#description' => t('Clean Slate - Truncate all advagg tables and delete all advagg files. Useful for testing purposes. Running this on a production site is not a good idea.'), + ); + $form['master_reset']['advagg_reset'] = array( + '#type' => 'submit', + '#value' => t('Master Reset'), + '#submit' => array('advagg_admin_master_reset'), + ); + $form['htaccess'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Rebuild htaccess files'), + '#description' => t('This will recreate the htaccess files located in the advagg_* directories.'), + ); + $form['htaccess']['advagg_recreate_htaccess'] = array( + '#type' => 'submit', + '#value' => t('Recreate htaccess files'), + '#submit' => array('advagg_admin_recreate_htaccess'), + ); + $form['bypass'] = array( + '#type' => 'fieldset', + '#title' => t('Aggregation Bypass Cookie'), + '#description' => t('This will set or remove a cookie that disables aggregation for the remainder of the browser session.'), + ); + $form['bypass']['submit'] = array( + '#type' => 'submit', + #'#button_type' => 'button', + '#value' => t('Toggle the "aggregation bypass cookie" for this browser'), + '#attributes' => array('onclick' => 'javascript:return advagg_toggle_cookie()'), + '#submit' => array('advagg_admin_toggle_bypass_cookie'), + ); + + $form['#submit'][] = 'advagg_admin_settings_form_submit'; + return system_settings_form($form); +} + +/** + * Validate form values. Used to unset variables before they get saved. + */ +function advagg_admin_settings_form_validate($form, &$form_state) { + global $conf; + + // Custom directory handling. + if (!empty($form_state['values']['advagg_custom_files_dir'])) { + $form_state['values']['advagg_custom_files_dir'] = trim($form_state['values']['advagg_custom_files_dir']); + $files_dir = $form_state['values']['advagg_custom_files_dir']; + if (!empty($files_dir) && $files_dir != $conf['advagg_custom_files_dir']) { + + // Try to create dir structure. + $cumulative = ''; + $path_array = explode('/', $files_dir); + foreach ($path_array as $dir) { + if (empty($cumulative)) { + $cumulative = $dir; + } + else { + $cumulative .= '/' . $dir; + } + // If dir creation fails, bail out of loop. + if (!file_check_directory($cumulative, FILE_CREATE_DIRECTORY)) { + break; + } + } + + // See if dir exists and is writable. + if (file_check_directory($files_dir) == TRUE) { + + // Remove old files. + list($css_path, $js_path) = advagg_get_root_files_dir(); + file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); + @unlink($css_path); + file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); + @unlink($js_path); + + // Set new path. + $conf['advagg_custom_files_dir'] = $files_dir; + advagg_get_root_files_dir(TRUE); + menu_rebuild(); + } + else { + form_set_error('advagg_custom_files_dir', t('%dir is not a directory or is not writable by the web server.', array('%dir' => $files_dir))); + } + } + } + + // If the IP field is not blank, check that its a valid address. + if (!empty($form_state['values']['advagg_server_addr']) && $form_state['values']['advagg_server_addr'] != -1 && ip2long($form_state['values']['advagg_server_addr']) === FALSE) { + form_set_error('advagg_server_addr', t('Must be a valid IP address.')); + } + + // Remove non variable form info. + unset($form_state['values']['advagg_flush']); + unset($form_state['values']['advagg_rebuild']); + unset($form_state['values']['advagg_forced_build']); + unset($form_state['values']['advagg_recreate_htaccess']); + unset($form_state['values']['advagg_reset']); +} + +/** + * Validate form values. Used to unset variables before they get saved. + */ +function advagg_admin_settings_form_submit($form, &$form_state) { + global $conf; + + // Gzip & htaccess checks. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $css_path .= '/.htaccess'; + $js_path .= '/.htaccess'; + + if ($conf['advagg_gzip_compression'] != $form_state['values']['advagg_gzip_compression'] && $form_state['values']['advagg_dir_htaccess']) { + $conf['advagg_gzip_compression'] = $form_state['values']['advagg_gzip_compression']; + $conf['advagg_dir_htaccess'] = $form_state['values']['advagg_dir_htaccess']; + advagg_admin_recreate_htaccess(); + } + if ($form_state['values']['advagg_dir_htaccess'] == FALSE) { + $conf['advagg_dir_htaccess'] = FALSE; + advagg_clearstatcache(TRUE, $css_path); + advagg_clearstatcache(TRUE, $js_path); + if (file_exists($css_path) || file_exists($js_path)) { + file_delete($css_path); + file_delete($js_path); + drupal_set_message(t('Advanced CSS/JS Aggregation directory level htaccess files have been removed.')); + } + } + elseif ($conf['advagg_dir_htaccess'] != $form_state['values']['advagg_dir_htaccess']) { + $conf['advagg_dir_htaccess'] = $form_state['values']['advagg_dir_htaccess']; + advagg_admin_recreate_htaccess(); + } + advagg_clearstatcache(TRUE, $css_path); + advagg_clearstatcache(TRUE, $js_path); + if ($conf['advagg_dir_htaccess'] && (!file_exists($css_path) || !file_exists($js_path))) { + advagg_admin_recreate_htaccess(); + } + + // If checksum mode changed, smart flush cache with new checksum mode selected. + if ($conf['advagg_checksum_mode'] != $form_state['values']['advagg_checksum_mode']) { + $conf['advagg_checksum_mode'] = $form_state['values']['advagg_checksum_mode']; + advagg_admin_flush_cache_button(); + } + + // If advagg is enabled/disabled, smart flush the cache. + if ($conf['advagg_enabled'] != $form_state['values']['advagg_enabled']) { + advagg_admin_flush_cache_button(); + } +} + +/** + * Master reset button. + */ +function advagg_admin_master_reset() { + cache_clear_all('*', 'cache_advagg', TRUE); + cache_clear_all('*', 'cache_advagg_files_data', TRUE); + cache_clear_all('*', 'cache_advagg_bundle_reuse', TRUE); + db_query("TRUNCATE TABLE {advagg_files}"); + db_query("TRUNCATE TABLE {advagg_bundles}"); + + module_invoke_all('advagg_master_reset'); + + list($css_path, $js_path) = advagg_get_root_files_dir(); + file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); + file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); + + drupal_set_message(t('Advanced CSS/JS Aggregation has been reset.')); +} + +/** + * Cache clear button. + */ +function advagg_admin_flush_cache_button() { + global $_advagg; + _drupal_flush_css_js(); + $cache_tables = advagg_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + + if (empty($_advagg['files'])) { + drupal_set_message(t('Advanced CSS/JS Aggregation cache scanned and no out of date bundles detected.')); + } + else { + if (empty($_advagg['rebuilt'])) { + drupal_set_message(t('Advanced CSS/JS Aggregation cache scanned and out of date bundles have been marked.
Old Files:
!files
Marked Bundles Count: %count', array( + '!files' => nl2br(filter_xss(implode("\n", $_advagg['files']))), + '%count' => count($_advagg['bundles']), + ) + )); + } + else { + drupal_set_message(t('Advanced CSS/JS Aggregation cache scanned and out of date bundles have been incremented and rebuilt.
Old Files:
%files
%count done.', array( + '%files' => implode('
', $_advagg['files']), + '%count' => count($_advagg['rebuilt']), + ) + )); + } + } +} + +/** + * Cache clear callback for admin_menu/flush-cache/advagg. + */ +function advagg_admin_flush_cache() { + advagg_admin_flush_cache_button(); + drupal_goto(); +} + +/** + * Set up batch for first and last name loading. + * + * This is where Drupal's Batch API comes into play. + * It's as simple as defining $batch, and then calling batch_set($batch) + */ +function advagg_admin_batch_rebuild($form, &$form_state) { + $batch = array(); + + // Set up the type of batch operation we will do. + if ($form_state['clicked_button']['#post']['op'] == t('Force all counters to be increment by one')) { + $increment = TRUE; + $batch['title'] = t('Increment and rebuild aggregated files'); + + } + else { + $increment = FALSE; + $batch['title'] = t('Rebuilding aggregated files'); + } + + // Build batch operational data. + $batch += array( + 'operations' => array( + array('advagg_admin_rebuild_bundles', array($increment)), + ), + 'finished' => 'advagg_admin_rebuild_bundles_done', + 'init_message' => t('Initializing...'), + 'progress_message' => t('@current of @total batch operations done.'), + 'error_message' => t('Rebuilding aggregated files encountered an error.'), + 'file' => drupal_get_path('module', 'advagg') . '/advagg.admin.inc', + ); + + // Run it + batch_set($batch); + batch_process('admin/settings/advagg'); +} + +/** + * Rebuild bundles. + */ +function advagg_admin_rebuild_bundles($increment, &$context) { + // init batch. + if (!isset($context['sandbox']['progress'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['max'] = db_result(db_query("SELECT COUNT(*) FROM (SELECT bundle_md5 FROM {advagg_bundles} GROUP BY bundle_md5) as temp")); + + // Increment All Counters. + if ($increment) { + db_query("UPDATE {advagg_bundles} SET counter = counter + 1"); + } + } + + // Set limit. + $limit = 1; + + // Get list of all bundles. + $result = db_query_range("SELECT bundle_md5 FROM {advagg_bundles} GROUP BY bundle_md5", $context['sandbox']['progress'], $limit); + while ($bundle_md5 = db_result($result)) { + $filenames = advagg_rebuild_bundle($bundle_md5, '', TRUE); + $filename = array(); + foreach ($filenames as $name => $data) { + $filename[] = $name; + } + + // Update our progress information. + $context['sandbox']['progress']++; + $context['message'] = t('%md5 Created', array('%md5' => $bundle_md5)); + $context['results'][] = $filename; + } + + // Inform the batch engine that we are not finished, + // and provide an estimation of the completion level we reached. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } +} + +function advagg_admin_rebuild_bundles_done($success, $results, $operations) { + if ($success) { + // Here we do something meaningful with the results. + $message = count($results) . ' files generated.'; + $message .= theme('item_list', $results); + } + else { + // An error occurred. + // $operations contains the operations that remained unprocessed. + $error_operation = reset($operations); + $message = t('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE))); + } + drupal_set_message($message); +} + +/** + * Rebuild htaccess files. + */ +function advagg_admin_recreate_htaccess() { + list($css_path, $js_path) = advagg_get_root_files_dir(); + $css_path .= '/.htaccess'; + $js_path .= '/.htaccess'; + + file_delete($css_path); + advagg_htaccess_check_generate($css_path, TRUE); + + file_delete($js_path); + advagg_htaccess_check_generate($js_path, TRUE); + + drupal_set_message(t('Advanced CSS/JS Aggregation htaccess files rebuilt.')); +} + +/** + * Set or remove the AdvAggDisabled cookie. + */ +function advagg_admin_toggle_bypass_cookie() { + global $base_path; + $cookie_name = 'AdvAggDisabled'; + $key = md5(drupal_get_private_key()); + + // If the cookie does exist then remove it. + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + setcookie($cookie_name, '', -1, $base_path, '.' . $_SERVER['HTTP_HOST']); + unset($_COOKIE[$cookie_name]); + drupal_set_message(t('AdvAgg Bypass Cookie Removed.')); + } + // If the cookie does not exist then set it. + else { + setcookie($cookie_name, $key, 0, $base_path, '.' . $_SERVER['HTTP_HOST']); + $_COOKIE[$cookie_name] = $key; + drupal_set_message(t('AdvAgg Bypass Cookie Set.')); + } +} + +function advagg_form_print_r($data) { + $data = print_r($data, TRUE); + $data = explode("\n", $data); + $new_data = ''; + foreach ($data as $text) { + $new_text = str_replace(' ', '', $text); + if ($new_text == "(") { + $new_data .= ' ' . $new_text; + } + else { + $new_data .= "\n" . $text; + } + } + $new_data = explode("\n", $new_data); + $new_data = array_filter($new_data); + $rows = count($new_data); + $new_data = implode("\n", $new_data); + return array($new_data, $rows); +} diff --git a/sites/all/modules/advagg/advagg.admin.js b/sites/all/modules/advagg/advagg.admin.js new file mode 100644 index 0000000..f23cd25 --- /dev/null +++ b/sites/all/modules/advagg/advagg.admin.js @@ -0,0 +1,30 @@ + +/** + * Toggle the advagg cookie + */ +function advagg_toggle_cookie() { + var cookie_name = 'AdvAggDisabled'; + + // See if the cookie exists. + var cookie_pos = document.cookie.indexOf(cookie_name + '=' + Drupal.settings.advagg.key); + + // If the cookie does exist then remove it. + if (cookie_pos !== -1) { + document.cookie = cookie_name + '=;' + + 'expires=-1;' + + ' path=' + Drupal.settings.basePath + ';' + + ' domain=.' + document.location.hostname + ';'; + alert(Drupal.t('AdvAgg Bypass Cookie Removed')); + } + // If the cookie does not exsit then set it. + else { + document.cookie = cookie_name + '=' + Drupal.settings.advagg.key + ';' + + ' path=' + Drupal.settings.basePath + ';' + + ' domain=.' + document.location.hostname + ';'; + alert(Drupal.t('AdvAgg Bypass Cookie Set')); + } + + // Must return false, if returning true then form gets submitted. + return false; +} + diff --git a/sites/all/modules/advagg/advagg.drush.inc b/sites/all/modules/advagg/advagg.drush.inc new file mode 100644 index 0000000..0e4bab4 --- /dev/null +++ b/sites/all/modules/advagg/advagg.drush.inc @@ -0,0 +1,49 @@ + implode("\n", $_advagg['files']), + '!count' => count($_advagg['bundles']), + ) + )); + } + else { + drush_log(dt("Advanced CSS/JS Aggregation cache scanned and out of date bundles have been incremented and rebuilt.\nOld Files:\n%files\n%count done.", array( + '%files' => implode("\n", $_advagg['files']), + '%count' => count($_advagg['rebuilt']), + ) + )); + } + } +} diff --git a/sites/all/modules/advagg/advagg.info b/sites/all/modules/advagg/advagg.info new file mode 100644 index 0000000..41ca1d3 --- /dev/null +++ b/sites/all/modules/advagg/advagg.info @@ -0,0 +1,11 @@ +name = Advanced CSS/JS Aggregation +description = Aggregates multiple CSS/JS files, serves them with gzip encoding and smart client-side cache headers. +package = Advanced CSS/JS Aggregation +core = 6.x + +; Information added by drupal.org packaging script on 2012-06-25 +version = "6.x-1.9" +core = "6.x" +project = "advagg" +datestamp = "1340665277" + diff --git a/sites/all/modules/advagg/advagg.install b/sites/all/modules/advagg/advagg.install new file mode 100644 index 0000000..c77b80e --- /dev/null +++ b/sites/all/modules/advagg/advagg.install @@ -0,0 +1,996 @@ + $t('Adv CSS/JS Agg - CSS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('CSS directory is not created or writable'), + 'description' => $t('%path is not setup correctly.', array('%path' => $css_path)), + ); + } + if (!file_check_directory($js_path, FILE_CREATE_DIRECTORY)) { + $requirements['advagg_js_path'] = array( + 'title' => $t('Adv CSS/JS Agg - JS Path'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('JS directory is not created or writable'), + 'description' => $t('%path is not setup correctly.', array('%path' => $js_path)), + ); + } + if (variable_get('preprocess_css', FALSE) || variable_get('preprocess_js', FALSE)) { + $requirements['advagg_core_off'] = array( + 'title' => $t('Adv CSS/JS Agg - Core Variables'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Cores CSS and/or JS aggregation is enabled'), + 'description' => $t('"Optimize CSS files" and "Optimize JavaScript files" on the performance page should be disabled.', array('@performance' => url('admin/settings/performance'))), + ); + } + if (variable_get('advagg_enabled', ADVAGG_ENABLED) == FALSE) { + $requirements['advagg_not_on'] = array( + 'title' => $t('Adv CSS/JS Agg - Enabled'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Advanced CSS/JS aggregation is not currently enabled.'), + 'description' => $t('Go to the Advanced CSS/JS aggregation settings page and enable it.', array('@settings' => url('admin/settings/advagg'))), + ); + } + if (module_exists('css_gzip')) { + $requirements['advagg_css_gzip'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Gzip'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The CSS Gzip module is enabled'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed.', array('@modules' => url('admin/build/modules'))), + ); + } + if (module_exists('csstidy')) { + $requirements['advagg_csstidy'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Tidy'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The CSS Tidy module is enabled'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed.', array('@modules' => url('admin/build/modules'))), + ); + } + if (module_exists('javascript_aggregator')) { + $requirements['advagg_javascript_aggregator'] = array( + 'title' => $t('Adv CSS/JS Agg - Javascript Aggregator'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The Javascript Aggregator is enabled'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed. Once uninstalled be sure to enable the "Use AdvAgg in closure" setting on the advagg config page', array( + '@modules' => url('admin/build/modules'), + '@config' => url('admin/settings/advagg/config'), + )), + ); + } + if (module_exists('unlimited_css')) { + $requirements['advagg_unlimited_css'] = array( + 'title' => $t('Adv CSS/JS Agg - IE Unlimited CSS Loader'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The IE Unlimited CSS Loader module is enabled'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed.', array('@modules' => url('admin/build/modules'))), + ); + } + if (module_exists('ie_css_optimizer')) { + $requirements['advagg_unlimited_css'] = array( + 'title' => $t('Adv CSS/JS Agg - IE CSS Optimizer'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The IE CSS Optimizer module is enabled'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed.', array('@modules' => url('admin/build/modules'))), + ); + } + if (module_exists('cmscdn')) { + $requirements['advagg_cmscdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CMS CDN'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The CMS CDN module is enabled.'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed.', array('@modules' => url('admin/build/modules'))), + ); + } + if (module_exists('bundlecache')) { + $requirements['advagg_bundlecache'] = array( + 'title' => $t('Adv CSS/JS Agg - BundleCache'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The BundleCache module is enabled.'), + 'description' => $t('On the modules page you can disable it, as this module is no longer needed.', array('@modules' => url('admin/build/modules'))), + ); + } + if (module_exists('css_emimage') && !function_exists('css_emimage_advagg_filenames_alter')) { + $requirements['advagg_css_emimage'] = array( + 'title' => $t('Adv CSS/JS Agg - CSS Embedded Images'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('CSS Embedded Images module needs a patch.'), + 'description' => $t('You need to apply the latest patch from this issue CSS Embedded Images - Add in support for advaggs hooks; otherwise these 2 modules are not compatible with each other.', array('@issue' => 'http://drupal.org/node/1078060')), + ); + } + if (isset($_advagg['closure']) && $_advagg['closure'] == FALSE) { + $requirements['advagg_closure'] = array( + 'title' => $t('Adv CSS/JS Agg - Closure'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Your theme implements its own closure function.'), + 'description' => $t('To solve this problem, you unfortunately have to modify your theme. Copy the phptemplate_closure() function found in advagg.module source code and integrate it into your theme phptemplate_closure() function or _closure() function.', array('@link' => 'http://drupalcode.org/project/advagg.git/blob/refs/heads/6.x-1.x:/advagg.module#l201')), + ); + } + if (module_exists('cdn')) { + $file_path_blacklist = variable_get(CDN_EXCEPTION_FILE_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_BLACKLIST_DEFAULT); + $file_path_blacklist = explode("\n", trim($file_path_blacklist)); + $file_path_blacklist = array_map('trim', $file_path_blacklist); + $file_path_blacklist = array_filter($file_path_blacklist); + if (array_search('*.js', $file_path_blacklist) !== FALSE) { + $requirements['advagg_cdn_js_blacklist'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Settings'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The CDN module is set to blacklist all *.js files.'), + 'description' => $t('Check your CDN settings and adjust the blacklist to not include "*.js". Testing is important in this case. "*tiny_mce.js" is a file/pattern we had to add to the blacklist after removing *.js from it.', array('@cdn' => url('admin/settings/cdn/other'))), + ); + } + } + init_theme(); + $hooks = theme_get_registry(); + // Test location of advagg_processor in $hooks['page']['preprocess functions']. + $preprocess_functions = advagg_install_theme_registry_location($hooks); + $function = end($preprocess_functions); + if ($function != 'advagg_processor') { + $requirements['advagg_theme'] = array( + 'title' => $t('Adv CSS/JS Agg - Theme Hook'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Theme hook is not in the correct place.'), + 'description' => $t('On the performance page clear the cache. If this is still an issue open up a bug on the advagg issue queue and be sure to include the "Hook Theme Info" output from the Advanced CSS/JS aggregation settings page. Current preprocess functions given: @list.', array( + '@performance' => url('admin/settings/performance'), + '@settings' => url('admin/settings/advagg'), + '@list' => implode(', ', $preprocess_functions), + )), + ); + } + // Test closure function. + $function = $hooks['closure']['function']; + if ($function != 'phptemplate_closure' && variable_get('advagg_closure', ADVAGG_CLOSURE)) { + $requirements['advagg_closure'] = array( + 'title' => $t('Adv CSS/JS Agg - Closure'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Closure theme function issue.') . $function, + 'description' => $t('On the performance page clear the cache or just the theme registry if you know how to do that. If this is still an issue open up a bug on the advagg issue queue.', array( + '@performance' => url('admin/settings/performance'), + )), + ); + } + $requirements += advagg_check_missing_handler(); + } + return $requirements; +} + +function advagg_install_theme_registry_location($hooks) { + // Define hooks that we can safely ignore, that get executed after advagg. + $ignored_hooks = array( + 'labjs_preprocess_page', + 'headjs_preprocess_page', + 'designkit_preprocess_page', + 'cdn_preprocess_page', + 'conditional_styles_preprocess_page', + ); + + // Call hook_advagg_theme_registry_alter(). + drupal_alter('advagg_theme_registry', $ignored_hooks); + + // Get preprocess hooks. + $hooks = $hooks['page']['preprocess functions']; + + // Removed ignored hooks. + foreach ($ignored_hooks as $hook) { + $key = array_search($hook, $hooks); + if ($key !== FALSE) { + unset($hooks[$key]); + } + } + + return $hooks; +} + +/** + * Check to see if the CSS/JS generator is working. + */ +function advagg_check_missing_handler() { + drupal_load('module', 'advagg'); + advagg_install_test_async_stream(); + + global $base_path; + $ret = array(); + $async = variable_get('advagg_async_generation', -1); + $filepath = '/css_missing' . mt_rand() . time() . '_0'; + + // Ensure translations don't break at install time + $t = get_t(); + + // Don't run the test if site is offline + if (variable_get('site_offline', 0)) { + $ret['advagg_async_offline'] = array( + 'title' => $t('Adv CSS/JS Agg - Asynchronous Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Unknown. Currently set to @value.', array('@value' => $async ? 'TRUE' : 'FALSE')), + 'description' => $t('This can not be tested while the site is in offline mode', array('@maintenance' => url('admin/settings/site-maintenance'))), + ); + return $ret; + } + + // Setup request + list($css_path, $js_path) = advagg_get_root_files_dir(); + $url = _advagg_build_url($css_path . $filepath . '.css'); + $headers = array( + 'Host' => $_SERVER['HTTP_HOST'], + 'Connection' => 'close', + ); + + // Check that the menu router handler is working. If it's not working the rest + // of the tests are pointless. + $item = menu_get_item($css_path . $filepath . '.css'); + if (empty($item['page_callback']) || strpos($item['page_callback'], 'advagg') === FALSE) { + $item = str_replace(' ', '    ', nl2br(htmlentities(print_r($item, TRUE)))); + $ret['advagg_async_generation_menu_issue'] = array( + 'title' => $t('Adv CSS/JS Agg - Async Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Flush your caches'), + 'description' => $t('You need to flush your menu cache. This can be done near the bottom of the performance page. If this does not fix the issue copy this info below when opening up an issue for advagg:
!info', array( + '@performance' => url('admin/settings/performance'), + '!info' => $item, + )), + ); + return $ret; + } + $item = menu_get_item($js_path . $filepath . '.js'); + if (empty($item['page_callback']) || strpos($item['page_callback'], 'advagg') === FALSE) { + $item = str_replace(' ', '    ', nl2br(htmlentities(print_r($item, TRUE)))); + $ret['advagg_async_generation_menu_issue'] = array( + 'title' => $t('Adv CSS/JS Agg - Async Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Flush your caches'), + 'description' => $t('You need to flush your menu cache. This can be done near the bottom of the performance page. If this does not fix the issue copy this info below when opening up an issue for advagg:
!info', array( + '@performance' => url('admin/settings/performance'), + '!info' => $item, + )), + ); + return $ret; + } + + // Send request and also time it. + timer_start(__FUNCTION__ . 'local'); + $data_local = drupal_http_request($url, $headers); + $time_local = timer_stop(__FUNCTION__ . 'local'); + + // Test CDN module; JS & CSS paths. + if (module_exists('cdn')) { + // Make sure CDN module is on. + $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); + if (($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) && !variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE)) { + + global $conf; + $path_blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT); + $conf[CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE] = ''; + + $url_cdn_css = advagg_build_uri($css_path . $filepath . '.css'); + $parts_css = @parse_url($url_cdn_css); + // Do not test CDN CSS if the hosts are the same. + if (!empty($parts_css['host']) && strcmp($parts_css['host'], $_SERVER['HTTP_HOST']) == 0) { + $parts_css = FALSE; + } + + $url_cdn_js = advagg_build_uri($js_path . $filepath . '.js'); + $parts_js = @parse_url($url_cdn_js); + // Do not test CDN JS if the hosts are the same. + if (!empty($parts_js['host']) && strcmp($parts_js['host'], $_SERVER['HTTP_HOST']) == 0) { + $parts_js = FALSE; + } + + $conf[CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE] = $path_blacklist; + + if (!empty($parts_css)) { + // Send request and also time it. + timer_start(__FUNCTION__ . 'cdn_css'); + $data_cdn_css = drupal_http_request($url_cdn_css); + $time_cdn_css = timer_stop(__FUNCTION__ . 'cdn_css'); + } + if (!empty($parts_js)) { + // Send request and also time it. + timer_start(__FUNCTION__ . 'cdn_js'); + $data_cdn_js = drupal_http_request($url_cdn_js); + $time_cdn_js = timer_stop(__FUNCTION__ . 'cdn_js'); + } + $mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC); + } + } + + $readme = drupal_get_path('module', 'advagg') . '/README.txt'; + + if (module_exists('cdn')) { + $cdn_extra = $t('(both fieldsets)'); + } + else { + $cdn_extra = ''; + } + + $extra_404 = $t('If you are still having issues you can go to the AdvAgg information tab and select Asynchronous debug info. If creating an issue on d.o be sure to include this information @cdn_extra.', + array( + '@info' => url('admin/settings/advagg/info'), + '@cdn_extra' => $cdn_extra, + ) + ); + $extra_404 .= module_exists('stage_file_proxy') ? $t('Stage File Proxy needs this patch applied to it in order for advagg to work correctly.') : ''; + + // Check CDN responses. + $cdn_failed = FALSE; + if (isset($parts_css) && empty($parts_css)) { + $ret['advagg_async_generation_css_cdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Async Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('CSS CDN Issue'), + 'description' => $t('Your current CDN settings are not rewriting the URL for advagg CSS files to point to your CDN. Most likely you need to adjust your CDN exceptions to allow CSS files from advagg.', array('@cdnother' => url('admin/settings/cdn/other'))), + ); + } + if (isset($parts_js) && empty($parts_js)) { + $ret['advagg_async_generation_js_cdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Async Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('JS CDN Issue'), + 'description' => $t('Your current CDN settings are not rewriting the URL for advagg JS files to point to your CDN. Most likely you need to adjust your CDN exceptions to allow JS files from advagg.', array('@cdnother' => url('admin/settings/cdn/other'))), + ); + } + if (isset($data_cdn_css)) { + if ($data_cdn_css->code != 200 && $mode == CDN_MODE_BASIC && (!empty($data_cdn_css->headers['X-AdvAgg']) || (!empty($data_cdn_css->data) && strpos($data_cdn_css->data, '') !== FALSE))) { + $ret['advagg_async_generation_css_cdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Async Mode'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('CSS CDN'), + 'description' => $t('Requests through the CDN are getting back to advagg for CSS files.'), + ); + } + else { + $cdn_failed = TRUE; + $ret['advagg_async_generation_css_cdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Async Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('CSS CDN Issue'), + 'description' => $t('Check your CDN settings; CSS request is not coming back when routed through the CDN. ', array('@cdn' => url('admin/settings/cdn'))) . $extra_404, + ); + } + } + if (isset($data_cdn_js)) { + if ($data_cdn_js->code != 200 && $mode == CDN_MODE_BASIC && (!empty($data_cdn_js->headers['X-AdvAgg']) || (!empty($data_cdn_js->data) && strpos($data_cdn_js->data, '') !== FALSE))) { + $ret['advagg_async_generation_js_cdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Async Mode'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('JS CDN'), + 'description' => $t('Requests through the CDN are getting back to advagg for JS files.'), + ); + } + else { + $cdn_failed = TRUE; + $ret['advagg_async_generation_js_cdn'] = array( + 'title' => $t('Adv CSS/JS Agg - CDN Async Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('JS CDN Issue'), + 'description' => $t('Check your CDN settings; JS request is not coming back when routed through the CDN. ', array('@cdn' => url('admin/settings/cdn'))) . $extra_404, + ); + } + } + + // Check normal response and set async variable accordingly. + if ($data_local->code != 200 && (!empty($data_local->headers['X-AdvAgg']) || strpos($data_local->data, '') !== FALSE)) { + // Hook menu works. + if ($async == -1 && !$cdn_failed) { + variable_set('advagg_async_generation', TRUE); + $ret['advagg_async_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Asynchronous Mode'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('Is now set to TRUE.'), + ); + } + if ($async == 0) { + $ret['advagg_async_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Asynchronous Mode'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('Could be changed to TRUE.'), + ); + } + if ($async == 1) { + $ret['advagg_async_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Asynchronous Mode'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('Already set to TRUE'), + ); + } + if ($cdn_failed) { + $ret['advagg_async_generation']['description'] = ' ' . $t('Be careful about enabling Asynchronous Mode. Your CDN is not forwarding 404s back to advagg missing file handling functions. There is a chance a user could have a broken experience.'); + } + } + else { + // Set to FALSE if not. + if ($async != 0) { + variable_set('advagg_async_generation', FALSE); + } + // Files htaccess check. Make sure RewriteEngine is not off. + $directory = file_directory_path(); + if (is_file($directory . '/.htaccess')) { + $file = file_get_contents($directory . '/.htaccess'); + if (stripos($file, "RewriteEngine off") !== FALSE) { + $ret['advagg_htaccess'] = array( + 'title' => $t('Adv CSS/JS Agg - Files Dir htaccess'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('The htaccess file in %dir is non standard.', array('%dir' => $directory)), + 'description' => $t('You need to modify your htaccess file in your files directory. See this link for more info.', array( + '@url' => 'http://drupal.org/node/1171244#comment-4770544', + )), + ); + } + } + + // Build error message. + if (empty($ret['advagg_htaccess'])) { + $ret['advagg_async_generation'] = array( + 'title' => $t('Adv CSS/JS Agg - Asynchronous Mode'), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Set to FALSE.'), + 'description' => $t('Check to see if you have fast 404s, if so create an exception for this module. The readme file explains what needs to be changed. You can try flushing the menu cache as well. ', array('@readme' => url($readme))) . $extra_404, + ); + } + } + + // Set socket timeout relative to local round trip. + $timeout = variable_get('advagg_socket_timeout', -1); + $new_time = ceil(($time_local['time'] + 51) / 1000); + if ($async) { + if ($timeout == -1 || $timeout != $new_time) { + variable_set('advagg_socket_timeout', $new_time); + $ret['advagg_socket_timeout'] = array( + 'title' => $t('Adv CSS/JS Agg - Socket Timeout'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('Set to %time seconds. Raw timer: %raw', array('%time' => $new_time, '%raw' => $time_local['time'])), + ); + } + else { + $ret['advagg_socket_timeout'] = array( + 'title' => $t('Adv CSS/JS Agg - Socket Timeout'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('Already set to %time seconds. Raw timer: %raw', array('%time' => $new_time, '%raw' => $time_local['time'])), + ); + } + } + + return $ret; +} + +/** + * Test if STREAM_CLIENT_ASYNC_CONNECT can be used. + */ +function advagg_install_test_async_stream() { + global $conf, $base_path; + + if (!function_exists('stream_socket_client') || !function_exists('stream_select')) { + return FALSE; + } + $advagg_async_socket_connect = variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT); + + // Build test request. + $url = _advagg_build_url(); + $headers = array( + 'Host' => $_SERVER['HTTP_HOST'], + 'Connection' => 'close', + ); + + // Request file. + $conf['advagg_async_socket_connect'] = TRUE; + advagg_async_connect_http_request($url, array('headers' => $headers)); + + // Send Request off. + $good = advagg_async_send_http_request(); + if ($good && !$advagg_async_socket_connect) { + variable_set('advagg_async_socket_connect', TRUE); + return TRUE; + } + else { + $conf['advagg_async_socket_connect'] = FALSE; + } +} + +/** + * Implementation of hook_schema(). + */ +function advagg_schema() { + // Create cache tables. + $schema['cache_advagg'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg']['description'] = t('Cache table for Advanced CSS/JS Aggregation. Used to keep timestamps and if the file exists.'); + + $schema['cache_advagg_files_data'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_files_data']['description'] = t('Cache table for Advanced CSS/JS Aggregation. Used to keep data about files.'); + + $schema['cache_advagg_bundle_reuse'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_bundle_reuse']['description'] = t('Cache table for Advanced CSS/JS Aggregation. Used to keep data about existing bundles that can be reused.'); + + // Create database tables. + $schema['advagg_files'] = array( + 'description' => t('Files used in CSS/JS aggregation.'), + 'fields' => array( + 'filename' => array( + 'description' => 'Path of the file relative to Drupal webroot.', + 'type' => 'text', + 'size' => 'normal', + 'not null' => TRUE, + ), + 'filename_md5' => array( + 'description' => 'MD5 hash of filename', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'checksum' => array( + 'description' => 'mtime or md5 of the files content.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'filetype' => array( + 'description' => 'Filetype.', + 'type' => 'varchar', + 'length' => 8, + 'not null' => TRUE, + 'default' => '', + ), + 'filesize' => array( + 'description' => 'The file size in bytes.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'counter' => array( + 'description' => 'This is incremented every time a file changes.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'checksum' => array('checksum'), + 'filetype' => array('filetype'), + 'filesize' => array('filesize'), + ), + 'primary key' => array('filename_md5'), + ); + + $schema['advagg_bundles'] = array( + 'description' => t('What files are used in what bundles.'), + 'fields' => array( + 'bundle_md5' => array( + 'description' => 'MD5 hash of the bundles list of files', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'filename_md5' => array( + 'description' => 'MD5 hash of filename source', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'counter' => array( + 'description' => 'This is incremented every time a file in the bundle changes.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'porder' => array( + 'description' => 'Processing order.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'root' => array( + 'description' => 'If 1 then it is a root file. 0 means not root file.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'timestamp' => array( + 'description' => 'Last used timestamp of the bundle.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'root' => array('root'), + 'timestamp' => array('timestamp'), + 'counter' => array('counter'), + 'root_timestamp' => array( + 'root', + 'timestamp', + ), + ), + 'primary key' => array('bundle_md5', 'filename_md5'), + ); + + return $schema; +} + +/** + * Update 6100 - Add new column to table. + */ +function advagg_update_6100() { + $ret = array(); + + // Add in data column + db_add_field($ret, 'advagg_files', 'data', array( + 'description' => 'Extra data about this file. Example would be what compressors work with it.', + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + )); + + return $ret; +} + +/** + * Update 6101 - Move data column to cache table. + */ +function advagg_update_6101() { + $ret = array(); + + // Make sure the advagg_set_file_data function is available. + drupal_load('module', 'advagg'); + + // Create cache table. + $schema['cache_advagg_files_data'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_files_data']['description'] = t('Cache table for Advanced CSS/JS Aggregation. Used to keep data about files.'); + db_create_table($ret, 'cache_advagg_files_data', $schema['cache_advagg_files_data']); + + // Migrate data. + $results = db_query("SELECT filename_md5, data FROM {advagg_files}"); + while ($row = db_fetch_array($results)) { + if (empty($row['data'])) { + continue; + } + $data = unserialize($row['data']); + advagg_set_file_data($row['filename_md5'], $data); + } + + // Drop in data column. + db_drop_field($ret, 'advagg_files', 'data'); + + + return $ret; +} + +/** + * Update 6102 - Add new field to table. + */ +function advagg_update_6102() { + $ret = array(); + + // Add in root column. + db_add_field($ret, 'advagg_bundles', 'root', array( + 'description' => 'If 1 then it is a root file. 0 means not root file.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + )); + + return $ret; +} + +/** + * Update 6103 - Create a bundle reuse cache table. + */ +function advagg_update_6103() { + $ret = array(); + + // Create cache table. + $schema['cache_advagg_bundle_reuse'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_bundle_reuse']['description'] = t('Cache table for Advanced CSS/JS Aggregation. Used to keep data about existing bundles that can be used.'); + db_create_table($ret, 'cache_advagg_bundle_reuse', $schema['cache_advagg_bundle_reuse']); + + return $ret; +} + +/** + * Update 6104 - Create a file builder cache table. + */ +function advagg_update_6104() { + $ret = array(); + + // Create cache table. + $schema['cache_advagg_file_builder'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_file_builder']['description'] = t('Cache table for Advanced CSS/JS Aggregation. Used to cache output from the advagg_css_js_file_builder function.'); + db_create_table($ret, 'cache_advagg_file_builder', $schema['cache_advagg_file_builder']); + + return $ret; +} + +/** + * Update 6105 - Remove strict JS variable and reset 2 advagg caches. + */ +function advagg_update_6105() { + $ret = array(); + + variable_del('advagg_strict_js_bundles'); + cache_clear_all('*', 'cache_advagg_bundle_reuse', TRUE); + cache_clear_all('*', 'cache_advagg_file_builder', TRUE); + $ret[] = array( + 'success' => TRUE, + 'query' => 'advagg bundle_reuse & file_builder caches flushed.', + ); + + return $ret; +} + +/** + * Update 6106 - Clear file cache. + */ +function advagg_update_6106() { + $ret = array(); + + cache_clear_all('*', 'cache_advagg_files_data', TRUE); + $ret[] = array( + 'success' => TRUE, + 'query' => 'advagg files_data cache flushed.', + ); + + return $ret; +} + +/** + * Update 6107 - Remove the cache_advagg_file_builder cache table. + */ +function advagg_update_6107() { + $ret = array(); + + if (db_table_exists('cache_advagg_file_builder')) { + cache_clear_all('*', 'cache_advagg_file_builder', TRUE); + $ret[] = array( + 'success' => TRUE, + 'query' => 'advagg file_builder cache flushed.', + ); + db_drop_table($ret, 'cache_advagg_file_builder'); + } + + return $ret; +} + +/** + * Update 6108 - Add new field & add indexes to advagg_bundles table. + */ +function advagg_update_6108() { + $ret = array(); + + // Make sure the advagg_get_root_files_dir function is available. + drupal_load('module', 'advagg'); + + // Add in timestamp column. + db_add_field($ret, 'advagg_bundles', 'timestamp', array( + 'description' => 'Last used timestamp of the bundle.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + )); + + // Add in indexes. + db_add_index($ret, 'advagg_bundles', 'root', array('root')); + db_add_index($ret, 'advagg_bundles', 'timestamp', array('timestamp')); + db_add_index($ret, 'advagg_bundles', 'counter', array('counter')); + + // Populate timestamps. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $results = db_query(" + SELECT + ab.bundle_md5, + ab.counter, + af.filetype + FROM {advagg_bundles} AS ab + INNER JOIN {advagg_files} AS af USING ( filename_md5 ) + GROUP BY bundle_md5"); + while ($row = db_fetch_array($results)) { + $filename = advagg_build_filename($row['filetype'], $row['bundle_md5'], $row['counter']); + if ($row['filetype'] == 'css') { + $file_type_path = $css_path; + } + else { + $file_type_path = $js_path; + } + $filepath = $file_type_path . '/' . $filename; + + $data = cache_get($filepath, 'cache_advagg'); + if (!empty($data->data)) { + // Set timestamp if it exists in the cache. + // Not using update_sql() so we can pass in % arguments. + db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", $data->data, $row['bundle_md5']); + $ret[] = array( + 'success' => TRUE, + 'query' => 'Timestamp added for: ' . $filename . '.', + ); + } + else { + $ret[] = array( + 'success' => TRUE, + 'query' => 'No timestamp found for: ' . $filename . '.', + ); + } + } + + $cache_tables = advagg_flush_caches(); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + $ret[] = array( + 'success' => TRUE, + 'query' => 'AdvAgg Caches Flushed.', + ); + + return $ret; +} + +/** + * Update 6109 - Add filesize field to advagg_files table. + */ +function advagg_update_6109() { + $ret = array(); + + // Make sure the advagg_get_root_files_dir function is available. + drupal_load('module', 'advagg'); + + // Add in timestamp column. + db_add_field($ret, 'advagg_files', 'filesize', array( + 'description' => 'The filesize of the file in bytes.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + )); + + // Add in an index. + db_add_index($ret, 'advagg_files', 'filesize', array('filesize')); + + // Populate the filesize column. + list($css_path, $js_path) = advagg_get_root_files_dir(); + $results = db_query("SELECT filename, filename_md5 FROM {advagg_files} AS advagg_files"); + while ($row = db_fetch_array($results)) { + $updated = FALSE; + if (@is_readable($row['filename'])) { + $size = filesize($row['filename']); + if (!empty($size)) { + db_query("UPDATE {advagg_files} SET filesize = %d WHERE filename_md5 = '%s'", $size, $row['filename_md5']); + $updated = TRUE; + $ret[] = array( + 'success' => TRUE, + 'query' => 'Filesize added for: ' . $row['filename'] . '.', + ); + } + } + if (!$updated) { + $ret[] = array( + 'success' => FALSE, + 'query' => 'Could not get the filesize for for: ' . $row['filename'] . '.', + ); + } + } + + return $ret; +} + +/** + * Update 6110 - Add index to advagg_bundles table. + */ +function advagg_update_6110() { + $ret = array(); + db_add_index($ret, 'advagg_bundles', 'root_timestamp', array('root', 'timestamp')); + return $ret; +} diff --git a/sites/all/modules/advagg/advagg.missing.inc b/sites/all/modules/advagg/advagg.missing.inc new file mode 100644 index 0000000..f2723cd --- /dev/null +++ b/sites/all/modules/advagg/advagg.missing.inc @@ -0,0 +1,351 @@ + 5) { + watchdog('advagg', 'This request could not generate correctly. Loop detected. Request data: %info', array('%info' => $_GET['q'])); + return t('In a Loop.'); + } + + // Counter in database. + $counter_in_db = db_result(db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $md5)); + if ($counter_in_db === FALSE) { + return t('Not a valid bundle.'); + } + // Cast counter as int + $counter_in_db = intval($counter_in_db); + $counter = intval($counter); + + // Set file(s) in cache to FALSE. + $arg[] = $filename; + cache_set(implode('/', $arg), FALSE, 'cache_advagg', TRUE); + advagg_missing_remove_cache($md5); + + // Build filepath. + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type == 'js') { + $file_type_path = $js_path; + } + if ($type == 'css') { + $file_type_path = $css_path; + } + $new_filename = advagg_build_filename($type, $md5, $counter); + $filepath = $file_type_path . '/' . $new_filename; + + // Only process if we got an older counter. + // If we have an out of range counter see if a simlar file exists and serve + // that up. + if ($counter > $counter_in_db || $counter < 0) { + $new_filename = advagg_build_filename($type, $md5, $counter_in_db); + $filepath = $file_type_path . '/' . $new_filename; + advagg_missing_send_file($filepath, advagg_build_uri($filepath), $type); + exit; + } + + // Break connection and do generation in the background. + if (!empty($_GET['generator'])) { + advagg_missing_async_opp($md5 . ' ' . $counter); + } + + // Rebuild file. + $conf['advagg_async_generation'] = FALSE; + $good = advagg_rebuild_bundle($md5, $counter, TRUE); + if (!$good) { + watchdog('advagg', 'This request could not generate correctly. Aggregate not generated. Request data: %info', array('%info' => $_GET['q'])); + return t('Rebuild Failed.'); + } + + // Serve direct or redirect to file. + $_GET['redirect_counter']++; + $uri = $base_path . $_GET['q'] . '?redirect_counter=' . $_GET['redirect_counter']; + advagg_missing_send_file($filepath, $uri, $type); + exit; +} + +/** + * Send the file or send a 307 redirect. + * + * @param $filepath + * filename + * @param $uri + * css or js + */ +function advagg_missing_send_file($filepath, $uri, $type) { + if (!headers_sent()) { + // Code from file_download. + if (file_exists($filepath)) { + $headers = module_invoke_all('file_download', $filepath, $type); + if ($key = array_search(-1, $headers)) { + unset($headers[$key]); + } + + // Remove all previously set Cache-Control headers, because we're going to + // override it. Since multiple Cache-Control headers might have been set, + // simply setting a new, overriding header isn't enough: that would only + // override the *last* Cache-Control header. Yay for PHP! + foreach ($headers as $key => $header) { + if (strpos($header, 'Cache-Control:') === 0) { + unset($headers[$key]); + } + elseif (strpos($header, 'ETag:') === 0) { + unset($headers[$key]); + } + } + if (function_exists('header_remove')) { + header_remove('Cache-Control'); + header_remove('ETag'); + } + else { + drupal_set_header("Cache-Control:"); + drupal_set_header("Cache-Control:"); + drupal_set_header("ETag:"); + drupal_set_header("ETag:"); + } + // Set a far future Cache-Control header (480 weeks), which prevents + // intermediate caches from transforming the data and allows any + // intermediate cache to cache it, since it's marked as a public resource. + $headers[] = "Cache-Control: max-age=290304000, no-transform, public"; + + if (count($headers)) { + advagg_missing_file_transfer($filepath, $headers); + } + } + + // advagg_missing_file_transfer didn't run/send data, redirect via header. + header('Location: ' . $uri, TRUE, 307); + usleep(250000); // Sleep for 250ms + } +} + +/** + * Set cache value to FALSE. + * + * @param $bundle_md5 + * Bundle's machine name. + */ +function advagg_missing_remove_cache($bundle_md5) { + $files = array(); + $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = '%s' ORDER BY porder ASC", $bundle_md5); + while ($row = db_fetch_array($results)) { + $files[] = $row['filename']; + $type = $row['filetype']; + } + + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type == 'js') { + $file_type_path = $js_path; + } + if ($type == 'css') { + $file_type_path = $css_path; + } + + $filenames = advagg_get_filename($files, $type, '', $bundle_md5); + if (!empty($filenames)) { + foreach ($filenames as $key => $info) { + $filename = $info['filename']; + $filepath = $file_type_path . '/' . $filename; + + cache_set($filepath, FALSE, 'cache_advagg', TRUE); + } + } +} + +/** + * Output text & set php in async mode. + * + * @param $output + * string - Text to output to open connection. + * @param $wait + * bool - Wait 1 second? + * @param $content_type + * string - Content type header. + * @param $length + * int - Content length. + */ +function advagg_missing_async_opp($output, $wait = TRUE, $content_type = "text/html; charset=utf-8", $length = 0) { + if (headers_sent()) { + return FALSE; + } + + // Calculate Content Length + if ($length == 0) { + $output .= "\n"; + $length = (advagg_missing_strlen($output) -1); + } + // Prime php for background operations + $loop = 0; + while (ob_get_level() && $loop < 25) { + ob_end_clean(); + $loop++; + } + header("Connection: close"); + ignore_user_abort(); + + // Output headers & data + ob_start(); + header("Content-type: " . $content_type); + header("Expires: Sun, 19 Nov 1978 05:00:00 GMT"); + header("Cache-Control: no-cache"); + header("Cache-Control: must-revalidate"); + header("Content-Length: " . $length); + header("Connection: close"); + print($output); + ob_end_flush(); + flush(); + // wait for 1 second + if ($wait) { + sleep(1); + } + + // text returned and connection closed. + // Do background processing. Time taken after should not effect page load times. + return TRUE; +} + +/** + * Get the length of a string in bytes + * + * @param $string + * get string length + */ +function advagg_missing_strlen($string) { + if (function_exists('mb_strlen')) { + return mb_strlen($string, '8bit'); + } + else { + return strlen($string); + } +} + +/** + * Transfer file using http to client. Pipes a file through Drupal to the + * client. + * + * @param $source File to transfer. + * @param $headers An array of http headers to send along with file. + */ +function advagg_missing_file_transfer($source, $headers) { + $source = advagg_missing_file_create_path($source); + $fd = fopen($source, 'rb'); + // Return if we can't open the file. Will try a 307 in browser to send file. + if (!$fd) { + return; + } + + // Clear the buffer. + if (ob_get_level()) { + ob_end_clean(); + } + + // Add in headers. + foreach ($headers as $header) { + // To prevent HTTP header injection, we delete new lines that are + // not followed by a space or a tab. + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + $header = preg_replace('/\r?\n(?!\t| )/', '', $header); + drupal_set_header($header); + } + + // Transfer file in 8096 byte chunks. + while (!feof($fd)) { + print fread($fd, 8096); + } + fclose($fd); + exit(); +} + +/** + * Make sure the destination is a complete path and resides in the file system + * directory, if it is not prepend the file system directory. + * + * @param $dest A string containing the path to verify. If this value is + * omitted, Drupal's 'files' directory will be used. + * @return A string containing the path to file, with file system directory + * appended if necessary, or FALSE if the path is invalid (i.e. outside the + * configured 'files' or temp directories). + */ +function advagg_missing_file_create_path($dest = 0) { + list($css_path, $js_path) = advagg_get_root_files_dir(); + + $valid_paths = array(); + $valid_paths[] = file_directory_path(); + $valid_paths[] = $css_path; + $valid_paths[] = $js_path; + foreach ($valid_paths as $file_path) { + if (!$dest) { + return $file_path; + } + // file_check_location() checks whether the destination is inside the Drupal files directory. + if (file_check_location($dest, $file_path)) { + return $dest; + } + // check if the destination is instead inside the Drupal temporary files directory. + elseif (file_check_location($dest, file_directory_temp())) { + return $dest; + } + // Not found, try again with prefixed directory path. + elseif (file_check_location($file_path . '/' . $dest, $file_path)) { + return $file_path . '/' . $dest; + } + } + // File not found. + return FALSE; +} diff --git a/sites/all/modules/advagg/advagg.module b/sites/all/modules/advagg/advagg.module new file mode 100644 index 0000000..6478e28 --- /dev/null +++ b/sites/all/modules/advagg/advagg.module @@ -0,0 +1,3816 @@ + 'advagg_missing_css', + 'type' => MENU_CALLBACK, + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + $items[$js_path . '/%'] = array( + 'page callback' => 'advagg_missing_js', + 'type' => MENU_CALLBACK, + 'access callback' => TRUE, + 'file path' => $file_path, + 'file' => 'advagg.missing.inc', + ); + $items['admin/settings/advagg'] = array( + 'title' => 'Advanced CSS/JS Aggregation', + 'description' => 'Configuration for Advanced CSS/JS Aggregation.', + 'page callback' => 'advagg_admin_page', + 'type' => MENU_NORMAL_ITEM, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg.admin.inc', + ); + $items['admin/settings/advagg/config'] = array( + 'title' => 'Configuration', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 0, + ); + $items['admin/settings/advagg/info'] = array( + 'title' => 'Information', + 'description' => 'More detailed information about advagg.', + 'page callback' => 'advagg_admin_info_page', + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg.admin.inc', + ); + $items['admin_menu/flush-cache/advagg'] = array( + 'page callback' => 'advagg_admin_flush_cache', + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg.admin.inc', + ); + return $items; +} + +/** + * Implementation of hook_admin_menu(). + * + * Add in a cache flush for advagg. + */ +function advagg_admin_menu(&$deleted) { + $links = array(); + + $links[] = array( + 'title' => 'Adv CSS/JS Agg', + 'path' => 'admin_menu/flush-cache/advagg', + 'query' => 'destination', + 'parent_path' => 'admin_menu/flush-cache', + ); + + return $links; +} + +/** + * Implementation of hook_admin_menu_output_alter(). + * + * Add in a cache flush for advagg. + */ +function advagg_admin_menu_output_alter(&$content) { + if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['advagg'])) { + $content['icon']['icon']['flush-cache']['advagg'] = $content['icon']['icon']['flush-cache']['requisites']; + $content['icon']['icon']['flush-cache']['advagg']['#title'] = t('Adv CSS/JS Agg'); + $content['icon']['icon']['flush-cache']['advagg']['#href'] = 'admin_menu/flush-cache/advagg'; + } +} + +/** + * Implementation of hook_cron(). + */ +function advagg_cron() { + if (!variable_get('advagg_prune_on_cron', ADVAGG_PRUNE_ON_CRON)) { + return; + } + + // Set the oldest file/bundle to keep at 2 weeks. + $max_time = module_exists('advagg_bundler') ? variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED) : 1209600; + $max_file_time = time() - $max_time; + $max_bundle_time = time() - ($max_time * 3); + $bundles_removed = 0; + $files_removed = array(); + + // Prune old files + $results = db_query("SELECT filename, filename_md5 FROM {advagg_files}"); + while ($row = db_fetch_array($results)) { + // If the file exists, do nothing + if (file_exists($row['filename'])) { + continue; + } + + // Remove bundles referencing missing files, if they are older than 2 weeks. + $bundles = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s' AND timestamp < %d", $row['filename_md5'], $max_file_time); + while ($bundle_md5 = db_result($bundles)) { + $bundles_removed++; + db_query("DELETE FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5); + } + $count = db_result(db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE filename_md5 = '%s'", $row['filename_md5'])); + + // If no more bundles reference the missing file then remove the file. + if (empty($count)) { + db_query("DELETE FROM {advagg_files} WHERE filename_md5 = '%s'", $row['filename_md5']); + $files_removed[] = $row['filename']; + } + } + + // Prune old bundles + $bundles_removed += db_result(db_query(" + SELECT COUNT(DISTINCT bundle_md5) AS advagg_count + FROM {advagg_bundles} + WHERE timestamp < %d + ", $max_bundle_time)); + $results = db_query("DELETE FROM {advagg_bundles} WHERE timestamp < %d", $max_bundle_time); + + // Report to watchdog if anything was done. + if (!empty($bundles_removed) || !empty($files_removed)) { + watchdog('advagg', 'Cron ran and the following files where removed from the database: %files
%count old bundles where also removed from the database.', array( + '%files' => implode(', ', $files_removed), + '%count' => $bundles_removed, + )); + } +} + +/** + * Implementation of hook_init(). + */ +function advagg_init() { + global $base_path, $conf, $_advagg; + + // Disable advagg if requested. + if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && user_access('bypass advanced aggregation')) { + $conf['advagg_enabled'] = FALSE; + $conf['advagg_use_full_cache'] = FALSE; + } + // Enable debugging if requested. + if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) { + $conf['advagg_debug'] = TRUE; + $conf['advagg_use_full_cache'] = FALSE; + } + // Enable core preprocessing if requested. + if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && user_access('bypass advanced aggregation')) { + $conf['preprocess_css'] = TRUE; + $conf['preprocess_js'] = TRUE; + $conf['advagg_use_full_cache'] = FALSE; + } + + // Disable ctools_ajax_page_preprocess() if this functionality is available. + if (variable_get('advagg_enabled', ADVAGG_ENABLED) && function_exists('ctools_ajax_run_page_preprocess')) { + ctools_ajax_run_page_preprocess(FALSE); + $_advagg['ctools_patched'] = TRUE; + } +} + +/** + * Implementation of hook_theme_registry_alter(). + * + * Make sure our preprocess function runs last for page. + * + * @param $theme_registry + * The existing theme registry data structure. + */ +function advagg_theme_registry_alter(&$theme_registry) { + global $_advagg; + if (isset($theme_registry['page'])) { + // If jquery_update's preprocess function is there already, remove it. + if (module_exists('jquery_update') && $key = array_search('jquery_update_preprocess_page', $theme_registry['page']['preprocess functions'])) { + unset($theme_registry['page']['preprocess functions'][$key]); + } + + // If ctools hasn't been patched remove it from getting pre-processed. + if ( !empty($_advagg['ctools_patched']) + && module_exists('ctools') + && $key = array_search('ctools_ajax_page_preprocess', $theme_registry['page']['preprocess functions']) + ) { + unset($theme_registry['page']['preprocess functions'][$key]); + } + + // Add our own preprocessing function to the end of the array. + $theme_registry['page']['preprocess functions'][] = 'advagg_processor'; + + // If labjs's is enabled, move it to the bottom. + if (module_exists('labjs') && $key = array_search('labjs_preprocess_page', $theme_registry['page']['preprocess functions'])) { + $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key]; + unset($theme_registry['page']['preprocess functions'][$key]); + } + + // If designkit is enabled, move it to the bottom. + if (module_exists('designkit') && $key = array_search('designkit_preprocess_page', $theme_registry['page']['preprocess functions'])) { + $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key]; + unset($theme_registry['page']['preprocess functions'][$key]); + } + + // If conditional styles is enabled, move it to the bottom. + if (module_exists('conditional_styles') && $key = array_search('conditional_styles_preprocess_page', $theme_registry['page']['preprocess functions'])) { + $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key]; + unset($theme_registry['page']['preprocess functions'][$key]); + } + } +} + +/** + * Get the CSS & JS path for advagg. + * + * @param $reset + * reset the static variables. + * @return + * array($css_path, $js_path) + */ +function advagg_get_root_files_dir($reset = FALSE) { + static $css_path = ''; + static $js_path = ''; + if ($reset) { + $css_path = ''; + $js_path = ''; + } + + if (!empty($css_path) && !empty($js_path)) { + return array($css_path, $js_path); + } + + $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + if (!$public_downloads) { + $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); + } + if (empty($custom_path)) { + $css_path = file_create_path('advagg_css'); + $js_path = file_create_path('advagg_js'); + return array($css_path, $js_path); + } + file_check_directory($custom_path, FILE_CREATE_DIRECTORY); + + // Get path name + if (!variable_get('advagg_no_conf_path', FALSE)) { + $conf_path = conf_path(); + if (is_link($conf_path)) { + $path = readlink($conf_path); + } + $conf_path = str_replace("\\", '/', $conf_path); + $conf_path = explode('/', $conf_path); + $conf_path = array_pop($conf_path); + $custom_path = $custom_path . '/' . $conf_path; + } + file_check_directory($custom_path, FILE_CREATE_DIRECTORY); + + $css_path = $custom_path . '/advagg_css'; + $js_path = $custom_path . '/advagg_js'; + file_check_directory($css_path, FILE_CREATE_DIRECTORY); + file_check_directory($js_path, FILE_CREATE_DIRECTORY); + return array($css_path, $js_path); +} + +/** + * Merge 2 css arrays together. + * + * @param $array1 + * first array + * @param $array2 + * second array + * @return + * combined array + */ +function advagg_merge_css($array1, $array2) { + if (!empty($array2)) { + foreach ($array2 as $media => $types) { + foreach ($types as $type => $files) { + foreach ($files as $file => $preprocess) { + $array1[$media][$type][$file] = $preprocess; + } + } + } + } + return $array1; +} + +/** + * Merge 2 css arrays together. + * + * @param $array1 + * first array + * @param $array2 + * second array + * @return + * combined array + */ +function advagg_merge_inline_css($array1, $array2) { + foreach ($array2 as $media => $types) { + foreach ($types as $type => $blobs) { + foreach ($blobs as $prefix => $data) { + foreach ($data as $suffix => $blob) { + $array1[$media][$type][$prefix][$suffix] = $blob; + } + } + } + } + return $array1; +} + +/** + * Remove .less files from the array. + * + * @param $css_func + * Drupal CSS array. + */ +function advagg_css_array_fixer(&$css_func) { + if (!module_exists('less')) { + return; + } + + // Remove '.css.less' files from the stack. + foreach ($css_func as $k => $v) { + foreach ($v as $ke => $va) { + foreach ($va as $file => $preprocess) { + if (substr($file, -5) === '.less') { + unset($css_func[$k][$ke][$file]); + } + } + } + } +} + +/** + * Remove css files from the array if there is a color module equivalent of it. + * + * @param $css_func + * Drupal CSS array. + */ +function advagg_color_css(&$css) { + if (!module_exists('color') || empty($css['all']['theme'])) { + return; + } + $file_path = file_directory_path(); + + // Find all color css files. + $files = array(); + foreach ($css['all']['theme'] as $file => $preprocess) { + if (strpos($file, $file_path) === FALSE) { + continue; + } + if (strpos($file, '/color/') === FALSE) { + continue; + } + $files[basename($file)] = $file; + } + + // Return if no color css files are found. + if (empty($files)) { + return; + } + + // Remove css files from the stack if there is a color variant of it. + foreach ($css['all']['theme'] as $file => $preprocess) { + $basename = basename($file); + if (isset($files[$basename]) && !in_array($file, $files)) { + unset($css['all']['theme'][$file]); + } + } +} + +/** + * See if a string ends with a substring. + * + * @param $haystack + * The main string being compared. + * @param $needle + * The secondary string being compared. + * @return + * bool + */ +function advagg_string_ends_with($haystack, $needle) { + // Define substr_compare if it doesn't exist (PHP 4 fix). + if (!function_exists('substr_compare')) { + /** + * Binary safe comparison of two strings from an offset, up to length + * characters. + * + * Compares main_str from position offset with str up to length characters. + * @see http://php.net/substr-compare#53084 + * + * @param $main_str + * The main string being compared. + * @param $str + * The secondary string being compared. + * @param $offset + * The start position for the comparison. If negative, it starts counting + * from the end of the string. + * @param $length + * The length of the comparison. The default value is the largest of the + * length of the str compared to the length of main_str less the offset. + * @param $case_insensitivity + * If TRUE, comparison is case insensitive. + * @return + * Returns < 0 if main_str from position offset is less than str, > 0 if + * it is greater than str, and 0 if they are equal. If offset is equal to + * or greater than the length of main_str or length is set and is less than + * 1, substr_compare() prints a warning and returns FALSE. + */ + function substr_compare($main_str, $str, $offset, $length = NULL, $case_insensitivity = FALSE) { + $offset = (int) $offset; + + // Throw a warning because the offset is invalid + if ($offset >= strlen($main_str)) { + trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING); + return FALSE; + } + + // We are comparing the first n-characters of each string, so let's use the PHP function to do it + if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE) { + return strncasecmp($main_str, $str, $length); + } + + // Get the substring that we are comparing + if (is_int($length)) { + $main_substr = substr($main_str, $offset, $length); + $str_substr = substr($str, 0, $length); + } + else { + $main_substr = substr($main_str, $offset); + $str_substr = $str; + } + + // Return a case-insensitive comparison of the two strings + if ($case_insensitivity === TRUE) { + return strcasecmp($main_substr, $str_substr); + } + + // Return a case-sensitive comparison of the two strings + return strcmp($main_substr, $str_substr); + } + } + + $haystack_len = strlen($haystack); + $needle_len = strlen($needle); + if ($needle_len > $haystack_len) { + return FALSE; + } + return substr_compare($haystack, $needle, $haystack_len -$needle_len, $needle_len, TRUE) === 0; +} + +/** + * Implementation of hook_advagg_disable_processor(). + */ +function advagg_advagg_disable_processor() { + // Disable advagg on the configuration page; in case something bad happened. + if (isset($_GET['q']) && + ( $_GET['q'] == 'admin/settings/advagg' + || $_GET['q'] == 'admin/settings/advagg/config' + || $_GET['q'] == 'batch' + ) + ) { + return TRUE; + } +} + +/** + * Return the server schema (http or https). + * + * @return string + * http OR https. + */ +function advagg_get_server_schema() { + return ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') + || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] == 'on') + ) ? 'https' : 'http'; +} + +/** + * Process variables for page.tpl.php + * + * @param $variables + * The existing theme data structure. + */ +function advagg_processor(&$variables) { + global $_advagg, $conf; + + // Invoke hook_advagg_disable_processor + $disabled = module_invoke_all('advagg_disable_processor'); + + // If disabled, skip + if (!variable_get('advagg_enabled', ADVAGG_ENABLED) || in_array(TRUE, $disabled, TRUE)) { + if (module_exists('jquery_update')) { + return jquery_update_preprocess_page($variables); + } + else { + return; + } + } + + // Do not use the cache if advagg is set in the URL. + if (isset($_GET['advagg']) && user_access('bypass advanced aggregation')) { + $conf['advagg_use_full_cache'] = FALSE; + } + // Do not use the cache if the disable cookie is set. + $cookie_name = 'AdvAggDisabled'; + $key = md5(drupal_get_private_key()); + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + $conf['advagg_use_full_cache'] = FALSE; + } + + // http or https. + $schema = advagg_get_server_schema(); + + // CSS + $css_var = $variables['css']; + $css_orig = $css_var; + if (!variable_get('advagg_only_css_from_variables', ADVAGG_ONLY_CSS_FROM_VARIABLES)) { + $css_func = drupal_add_css(); + advagg_css_array_fixer($css_func); + } + else { + $css_func = array(); + } + $css = advagg_merge_css($css_var, $css_func); + $css_func_inline = advagg_add_css_inline(); + if (!empty($css_func_inline)) { + $css = advagg_merge_inline_css($css, $css_func_inline); + } + $css_conditional_styles = !empty($variables['conditional_styles']) ? $variables['conditional_styles'] : ''; + $css_styles = $variables['styles']; + advagg_color_css($css); + + // Try cache. + if (variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE)) { + // Build the cache ID + // md5 of the CSS input + // http or https + // hostname + // the js rendering function + // css/js query string + $cid = 'advagg_processor:css:' + . md5(serialize(array($css, $css_conditional_styles))) . ':' + . $schema . ':' + . $_SERVER['HTTP_HOST'] . ':' + . variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION) . ':' + . substr(variable_get('css_js_query_string', '0'), 0, 1); + $cache = cache_get($cid, 'cache_advagg_bundle_reuse'); + } + elseif (isset($cid)) { + unset($cid); + } + if (!empty($cache->data)) { + $variables['styles'] = $cache->data; + } + else { + // Build HTML code. + $processed_css = advagg_process_css($css); + if (!empty($processed_css)) { + $variables['styles'] = $processed_css; + } + $variables['styles'] .= $css_conditional_styles; + + // Cache output. + if (isset($cid) && variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE) && lock_acquire($cid)) { + cache_set($cid, $variables['styles'], 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + lock_release($cid); + } + } + + // JS + $js_code = array(); + $js_code['header'] = drupal_add_js(NULL, NULL, 'header'); + if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) { + $js_code['footer'] = drupal_add_js(NULL, NULL, 'footer'); + } + $skip_keys = variable_get('advagg_region_skip_keys', array('styles', 'scripts', 'zebra', 'id', 'directory', 'layout', 'head_title', 'base_path', 'front_page', 'head', 'body_classes', 'header', 'footer', 'closure')); + foreach ($variables as $key => $value) { + if (!in_array($key, $skip_keys) && is_string($value) && !empty($value) && !isset($js_code[$key])) { + $js_code[$key] = drupal_add_js(NULL, NULL, $key); + } + } + $js_code_orig = $js_code; + + // Try cache. + if (variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE)) { + // Build the cache ID + // md5 of the JS input + // http or https + // hostname + // the js rendering function + // css/js query string + $cid = 'advagg_processor:js:' + . md5(serialize($js_code)) . ':' + . $schema . ':' + . $_SERVER['HTTP_HOST'] . ':' + . variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION) . ':' + . substr(variable_get('css_js_query_string', '0'), 0, 1); + $cache = cache_get($cid, 'cache_advagg_bundle_reuse'); + } + elseif (isset($cid)) { + unset($cid); + } + if (!empty($cache->data)) { + $js_code = $cache->data; + } + else { + // Build HTML code. + advagg_jquery_updater($js_code['header']); + $js_code = advagg_process_js($js_code); + + // Cache array. + if (isset($cid) && variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE) && lock_acquire($cid)) { + cache_set($cid, $js_code, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + lock_release($cid); + } + } + + // Place JS in the correct places. + foreach ($js_code as $key => $value) { + if ($key == 'header') { + $variables['scripts'] = $value; + } + elseif ($key == 'footer' && variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) { + $variables['closure'] .= $value; + } + else { + $variables[$key] .= $value; + } + } + + // Send requests to server if async enabled. + advagg_async_send_http_request(); + + // Write debug info to watchdog if debugging enabled. + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $data = array( + 'css_before_vars' => $css_orig, + 'css_before_function' => $css_func, + 'css_before_styles' => $css_styles, + 'css_before_inline' => $css_func_inline, + 'css_before_conditional_styles' => $css_conditional_styles, + 'css_merged' => $css, + 'css_after' => $processed_css, + 'js_before' => $js_code_orig, + 'js_after' => $js_code, + ); + $data['runtime'] = isset($_advagg['debug']) ? $_advagg['debug'] : FALSE; + $data = str_replace(' ', '    ', nl2br(htmlentities(iconv('utf-8', 'utf-8//IGNORE', print_r($data, TRUE)), ENT_QUOTES, 'UTF-8'))); + watchdog('advagg', 'Debug info: !data', array('!data' => $data), WATCHDOG_DEBUG); + } +} + +/** + * Special handling for jquery update. + * + * @param $js + * List of files in the header + */ +function advagg_jquery_updater(&$js) { + if (!module_exists('jquery_update') || !variable_get('jquery_update_replace', TRUE) || empty($js)) { + return; + } + + // Replace jquery.js first. + $new_jquery = array(jquery_update_jquery_path() => $js['core']['misc/jquery.js']); + $js['core'] = array_merge($new_jquery, $js['core']); + unset($js['core']['misc/jquery.js']); + + // Loop through each of the required replacements. + $replacement_path = drupal_get_path('module', 'jquery_update') . '/replace/'; + foreach (jquery_update_get_replacements() as $type => $replacements) { + foreach ($replacements as $find => $replace) { + // If the file to replace is loaded on this page... + if (isset($js[$type][$find])) { + // Create a new entry for the replacement file, and unset the original one. + $replace = $replacement_path . $replace; + $js[$type][$replace] = $js[$type][$find]; + unset($js[$type][$find]); + } + } + } +} + +/** + * Given a list of files; return back the aggregated filename. + * + * @param $files + * List of files in the proposed bundle. + * @param $filetype + * css or js. + * @param $counter + * (optional) Counter value. + * @param $bundle_md5 + * (optional) Bundle's machine name. + * @return + * Aggregated filename. + */ +function advagg_get_filename($files, $filetype, $counter = FALSE, $bundle_md5 = '') { + if (empty($files) || empty($filetype)) { + return FALSE; + } + + global $_advagg; + $filenames = array(); + + $run_alter = FALSE; + if (empty($bundle_md5)) { + // Create bundle md5 + $schema = advagg_get_server_schema(); + $bundle_md5 = md5($schema . implode('', $files)); + $run_alter = TRUE; + + // Record root request in db. + // Get counter if there. + if (empty($counter)) { + $counter = db_result(db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); + } + // If this is a brand new bundle then insert file/bundle info into database. + if ($counter === FALSE) { + $counter = 0; + advagg_insert_bundle_db($files, $filetype, $bundle_md5, TRUE); + } + // If bundle should be root and is not, then make it root. + // Refresh timestamp if older then 12 hours. + $row = db_fetch_array(db_query("SELECT root, timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); + if ($row['root'] === 0 || time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) { + db_query("UPDATE {advagg_bundles} SET root = '1', timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5); + } + } + + // Set original array. + $filenames[] = array( + 'filetype' => $filetype, + 'files' => $files, + 'counter' => $counter, + 'bundle_md5' => $bundle_md5, + ); + + // Invoke hook_advagg_filenames_alter() to give installed modules a chance to + // alter filenames. One to many relationships come to mind. + // Do not run alter if MD5 was given, we want to generate that file only in + // this special case. + if ($run_alter) { + // Force counter to be looked up later. + $filenames[0]['counter'] = FALSE; + drupal_alter('advagg_filenames', $filenames); + } + + // Write to DB if needed and create filenames. + $output = array(); + $used_md5 = array(); + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $_advagg['debug']['get_filename_post_alter'][] = array( + 'key' => $bundle_md5, + 'filenames' => $filenames, + ); + } + + // Get all counters at once + $counters = array(); + foreach ($filenames as $key => $values) { + if (empty($values['counter'])) { + $counters[$key] = $values['bundle_md5']; + } + } + $result = advagg_db_multi_select_in('advagg_bundles', 'bundle_md5', "'%s'", $counters, array('counter', 'bundle_md5'), 'GROUP BY bundle_md5'); + while ($row = db_fetch_array($result)) { + $key = array_search($row['bundle_md5'], $counters); + if (empty($filenames[$key]['counter']) && $filenames[$key]['counter'] !== 0) { + $filenames[$key]['counter'] = intval($row['counter']); + } + } + + foreach ($filenames as $values) { + // Get info from array. + $filetype = $values['filetype']; + $files = $values['files']; + $counter = $values['counter']; + $bundle_md5 = $values['bundle_md5']; + + // See if a JS bundle exists that already has the same files in it, just in a + // different order. +// if ($filetype == 'js' && $run_alter) { +// advagg_find_existing_bundle($files, $bundle_md5); +// } + + // Do not add the same bundle twice. + if (isset($used_md5[$bundle_md5])) { + continue; + } + $used_md5[$bundle_md5] = TRUE; + + // If this is a brand new bundle then insert file/bundle info into database. + if (empty($counter) && $counter !== 0) { + $counter = 0; + advagg_insert_bundle_db($files, $filetype, $bundle_md5, FALSE); + } + + // Prefix filename to prevent blocking by firewalls which reject files + // starting with "ad*". + $output[] = array( + 'filename' => advagg_build_filename($filetype, $bundle_md5, $counter), + 'files' => $files, + 'bundle_md5' => $bundle_md5, + ); + + // Refresh timestamp if older then 12 hours. + $row = db_fetch_array(db_query("SELECT timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); + if (time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) { + db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5); + } + + } + return $output; +} + +/** + * Get a bundle from the cache & verify it is good. + * + * @param $cached_data_key + * cache key for the cache_advagg_bundle_reuse table. + * @param $debug_name + * Name to output in the array if debugging is enabled. + * @return + * data from the cache. + */ +function advagg_cached_bundle_get($cached_data_key, $debug_name) { + global $_advagg; + $data = cache_get($cached_data_key, 'cache_advagg_bundle_reuse'); + if (!empty($data->data)) { + $data = $data->data; + $bundle_contents = array(); + $good = TRUE; + // Debugging. + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + // Verify cached data is good. + foreach ($data as $filename => $extra) { + if (is_numeric($filename)) { + continue; + } + // Get md5 from aggregated filename. + $b_md5 = explode('/', $filename); + $b_md5 = explode('_', array_pop($b_md5)); + $b_md5 = $b_md5[1]; + + // Lookup bundle and make sure it is valid. + if (!empty($b_md5)) { + list($b_filetype, $b_files) = advagg_get_files_in_bundle($b_md5); + $bundle_contents[$filename] = $b_files; + if (empty($b_files)) { + $good = FALSE; + } + } + } + + $_advagg['debug'][$debug_name][] = array( + 'key' => $cached_data_key, + 'cache' => $data, + 'bundle_contents' => $bundle_contents, + ); + } + if ($good) { + return $data; + } + } + return FALSE; +} + +/** + * Given a list of files, see if a bundle already exists containing all of those + * files. If in strict mode then the file count has to be the same. + * + * @param $files + * List of files in the proposed bundle. + * @param $bundle_md5 + * Bundle's machine name. + */ +function advagg_find_existing_bundle(&$files, &$bundle_md5) { + // Sort files for better cache hits. + $temp_files = $files; + sort($temp_files); + $schema = advagg_get_server_schema(); + $cached_data_key = 'advagg_existing_' . md5($schema . implode('', $temp_files)); + + // Try cache first; cache table is cache_advagg_bundle_reuse. string is debug name. + $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_find_existing_bundle'); + if (!empty($cached_data)) { + $files = $cached_data[0]['files']; + $bundle_md5 = $cached_data[0]['bundle_md5']; + return; + } + + // Build union query. + $query = 'SELECT root.bundle_md5 FROM {advagg_bundles} AS root'; + $joins = array(); + $wheres = array(); + $args = array(); + $counter = 0; + foreach ($files as $filename) { + // Use alpha for table aliases; numerics do not work. + $key = strtr($counter, '01234567890', 'abcdefghij'); + + $joins[$key] = "\nINNER JOIN {advagg_bundles} AS $key USING(bundle_md5)\n"; + if ($counter == 0) { + $wheres[$key] = "WHERE $key.filename_md5 = '%s'"; + } + else { + $wheres[$key] = "AND $key.filename_md5 = '%s'"; + } + $args[$key] = md5($filename); + $counter++; + } + $query .= implode("\n", $joins); + $query .= implode("\n", $wheres); + $query .= ' GROUP BY bundle_md5'; + + // Find matching bundles and select first good one. + $files_count = count($files); + $results = db_query($query, $args); + while ($new_md5 = db_result($results)) { + $count = db_result(db_query("SELECT count(*) FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $new_md5)); + // Make sure bundle has the same number of files if using strict matching. + if (!empty($count) && $count == $files_count) { + $bundle_md5 = $new_md5; + $data = array(array( + 'files' => $files, + 'bundle_md5' => $bundle_md5, + )); + cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + return; + } + } +} + +/** + * Build the filename. + * + * @param $filetype + * css or js. + * @param $counter + * Counter value. + * @param $bundle_md5 + * Bundle's machine name. + */ +function advagg_build_filename($filetype, $bundle_md5, $counter) { + return $filetype . '_' . $bundle_md5 . '_' . $counter . '.' . $filetype; +} + +/** + * Insert info into the advagg_files and advagg_bundles database. + * + * @param $files + * List of files in the proposed bundle. + * @param $filetype + * css or js. + * @param $bundle_md5 + * Bundle's machine name. + * @param $root + * Is this a root bundle. + */ +function advagg_insert_bundle_db($files, $filetype, $bundle_md5, $root) { + $lock_name = 'advagg_insert_bundle_db' . $bundle_md5; + if (!lock_acquire($lock_name)) { + // If using async, wait before returning to give the other request time + // to complete. + if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2) { + lock_wait($lock_name); + } + return; + } + + // Double check that the bundle doesn't exist now that we are in a lock. + $bundle_exists = db_result(db_query("SELECT 1 FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); + if ($bundle_exists) { + lock_release($lock_name); + return; + } + + foreach ($files as $order => $filename) { + $filename_md5 = md5($filename); + + // Insert file into the advagg_files table if it doesn't exist. + $checksum = db_result(db_query("SELECT checksum FROM {advagg_files} WHERE filename_md5 = '%s'", $filename_md5)); + if (empty($checksum)) { + $checksum = advagg_checksum($filename); + db_query("INSERT INTO {advagg_files} (filename, filename_md5, checksum, filetype, filesize) VALUES ('%s', '%s', '%s', '%s', %d)", $filename, $filename_md5, $checksum, $filetype, @filesize($filename)); + } + + // Create the entries in the advagg_bundles table. + db_query("INSERT INTO {advagg_bundles} (bundle_md5, filename_md5, counter, porder, root, timestamp) VALUES ('%s', '%s', '%d', '%d', '%d', '%d')", $bundle_md5, $filename_md5, 0, $order, $root, time()); + } + + lock_release($lock_name); +} + +/** + * Save a string to the specified destination. Verify that file size is not zero. + * + * @param $data + * A string containing the contents of the file. + * @param $dest + * A string containing the destination location. + * @return + * Boolean indicating if the file save was successful. + */ +function advagg_file_saver($data, $dest, $force, $type) { + // Make sure the tmp folder is ready for use + $tmp = file_directory_temp(); + file_check_directory($tmp, FILE_CREATE_DIRECTORY); + + // Create the JS file. + $file_save_data = 'file_save_data'; + $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); + if (!empty($custom_path)) { + $file_save_data = 'advagg_file_save_data'; + } + + if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + + // Make sure filesize is not zero. + advagg_clearstatcache(TRUE, $dest); + if (@filesize($dest) == 0 && !empty($data)) { + if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + advagg_clearstatcache(TRUE, $dest); + if (@filesize($dest) == 0 && !empty($data)) { + // Filename is bad, create a new one next time. + file_delete($dest); + return FALSE; + } + } + + if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION) && extension_loaded('zlib')) { + $gzip_dest = $dest . '.gz'; + advagg_clearstatcache(TRUE, $gzip_dest); + if (!file_exists($gzip_dest) || $force) { + $gzip_data = gzencode($data, 9, FORCE_GZIP); + if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + + // Make sure filesize is not zero. + advagg_clearstatcache(TRUE, $gzip_dest); + if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) { + if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + advagg_clearstatcache(TRUE, $gzip_dest); + if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) { + // Filename is bad, create a new one next time. + file_delete($gzip_dest); + return FALSE; + } + } + } + } + + // Make sure .htaccess file exists. + advagg_htaccess_check_generate($dest); + + cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT); + return TRUE; +} + + + +/** + * ***MODIFIED CORE FUNCTIONS BELOW*** + * + * @see file_save_data() + * @see file_move() + * @see file_copy() + */ + +/** + * Save a string to the specified destination. + * + * @see file_save_data() + * + * @param $data A string containing the contents of the file. + * @param $dest A string containing the destination location. + * @param $replace Replace behavior when the destination file already exists. + * - FILE_EXISTS_REPLACE - Replace the existing file + * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique + * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * + * @return A string containing the resulting filename or 0 on error + */ +function advagg_file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) { + $temp = file_directory_temp(); + // On Windows, tempnam() requires an absolute path, so we use realpath(). + $file = tempnam(realpath($temp), 'file'); + if (!$fp = fopen($file, 'wb')) { + drupal_set_message(t('The file could not be created.'), 'error'); + return 0; + } + fwrite($fp, $data); + fclose($fp); + + if (!advagg_file_move($file, $dest, $replace)) { + return 0; + } + + return $file; +} + +/** + * Moves a file to a new location. + * + * @see file_move() + * + * - Checks if $source and $dest are valid and readable/writable. + * - Performs a file move if $source is not equal to $dest. + * - If file already exists in $dest either the call will error out, replace the + * file or rename the file based on the $replace parameter. + * + * @param $source + * Either a string specifying the file location of the original file or an + * object containing a 'filepath' property. This parameter is passed by + * reference and will contain the resulting destination filename in case of + * success. + * @param $dest + * A string containing the directory $source should be copied to. If this + * value is omitted, Drupal's 'files' directory will be used. + * @param $replace + * Replace behavior when the destination file already exists. + * - FILE_EXISTS_REPLACE: Replace the existing file. + * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is + * unique. + * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * @return + * TRUE for success, FALSE for failure. + */ +function advagg_file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { + $path_original = is_object($source) ? $source->filepath : $source; + + if (advagg_file_copy($source, $dest, $replace)) { + $path_current = is_object($source) ? $source->filepath : $source; + + if ($path_original == $path_current || file_delete($path_original)) { + return 1; + } + drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error'); + } + return 0; +} + +/** + * Copies a file to a new location. + * + * @see file_copy() + * + * This is a powerful function that in many ways performs like an advanced + * version of copy(). + * - Checks if $source and $dest are valid and readable/writable. + * - Performs a file copy if $source is not equal to $dest. + * - If file already exists in $dest either the call will error out, replace the + * file or rename the file based on the $replace parameter. + * + * @param $source + * Either a string specifying the file location of the original file or an + * object containing a 'filepath' property. This parameter is passed by + * reference and will contain the resulting destination filename in case of + * success. + * @param $dest + * A string containing the directory $source should be copied to. If this + * value is omitted, Drupal's 'files' directory will be used. + * @param $replace + * Replace behavior when the destination file already exists. + * - FILE_EXISTS_REPLACE: Replace the existing file. + * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is + * unique. + * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * @return + * TRUE for success, FALSE for failure. + */ +function advagg_file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { + $directory = dirname($dest); + + // Process a file upload object. + if (is_object($source)) { + $file = $source; + $source = $file->filepath; + if (!$basename) { + $basename = $file->filename; + } + } + + $source = realpath($source); + advagg_clearstatcache(TRUE, $source); + if (!file_exists($source)) { + drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); + return 0; + } + + // If the destination file is not specified then use the filename of the source file. + $basename = basename($dest); + $basename = $basename ? $basename : basename($source); + $dest = $directory . '/' . $basename; + + // Make sure source and destination filenames are not the same, makes no sense + // to copy it if they are. In fact copying the file will most likely result in + // a 0 byte file. Which is bad. Real bad. + if ($source != realpath($dest)) { + if (!$dest = file_destination($dest, $replace)) { + drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error'); + return FALSE; + } + + if (!@copy($source, $dest)) { + drupal_set_message(t('The selected file %file could not be copied. ' . $dest, array('%file' => $source)), 'error'); + return 0; + } + + // Give everyone read access so that FTP'd users or + // non-webserver users can see/read these files, + // and give group write permissions so group members + // can alter files uploaded by the webserver. + @chmod($dest, 0664); + } + + if (isset($file) && is_object($file)) { + $file->filename = $basename; + $file->filepath = $dest; + $source = $file; + } + else { + $source = $dest; + } + + return 1; // Everything went ok. +} + +/** + * Generate a checksum for a given filename. + * + * @param $filename + * filename + * @return + * Checksum value. + */ +function advagg_checksum($filename) { + advagg_clearstatcache(TRUE, $filename); + if (file_exists($filename)) { + $mode = variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE); + if ($mode == 'mtime') { + $checksum = @filemtime($filename); + if ($checksum === FALSE) { + touch($filename); + advagg_clearstatcache(TRUE, $filename); + $checksum = @filemtime($filename); + // Use md5 as a last option. + if ($checksum === FALSE) { + $checksum = md5(file_get_contents($filename)); + } + } + } + elseif ($mode = 'md5') { + $checksum = md5(file_get_contents($filename)); + } + } + else { + $checksum = '-1'; + } + return $checksum; +} + +/** + * See if this bundle has been built. + * + * @param $filepath + * filename + * @return + * Boolean indicating if the bundle already exists. + */ +function advagg_bundle_built($filepath) { + // Don't use the cache if not selected. + if (!variable_get('advagg_bundle_built_mode', ADVAGG_BUNDLE_BUILT_MODE)) { + advagg_clearstatcache(TRUE, $filepath); + return file_exists($filepath); + } + + $data = advagg_get_bundle_from_filename(basename($filepath)); + if (is_array($data)) { + list($type, $md5, $counter) = $data; + } + else { + return FALSE; + } + + $data = cache_get($filepath, 'cache_advagg'); + if (isset($data->data)) { + // Refresh timestamp if older then 12 hours. + if (time() - $data->data > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) { + cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT); + db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5); + } + return TRUE; + } + + // If not in cache check disk. + advagg_clearstatcache(TRUE, $filepath); + if (file_exists($filepath)) { + if (@filesize($filepath) == 0) { + return FALSE; + } + } + else { + return FALSE; + } + // File existed on disk; place in cache. + cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT); + db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5); + return TRUE; +} + +function advagg_get_bundle_from_filename($filename) { + // Verify requested filename has the correct pattern. + if (!preg_match('/^(j|cs)s_[0-9a-f]{32}_\d+\.(j|cs)s$/', $filename)) { + return t('Wrong Pattern.'); + } + + // Get type + $type = substr($filename, 0, strpos($filename, '_')); + + // Get extension + $ext = substr($filename, strpos($filename, '.', 37) + 1); + + // Make sure extension is the same as the type. + if ($ext != $type) { + return t('Type does not match extension.'); + } + + // Extract info from wanted filename. + if ($type == 'css') { + $md5 = substr($filename, 4, 32); + $counter = substr($filename, 37, strpos($filename, '.', 38) -37); + } + elseif ($type == 'js') { + $md5 = substr($filename, 3, 32); + $counter = substr($filename, 36, strpos($filename, '.', 37) -36); + } + else { + return t('Wrong file type.'); + } + + return array($type, $md5, $counter); +} + +/** + * Implementation of hook_flush_caches(). + */ +function advagg_flush_caches() { + // Try to allocate enough time to flush the cache + if (function_exists('set_time_limit')) { + @set_time_limit(240); + } + + global $_advagg; + // Only one advagg cache flusher can run at a time. + if (!lock_acquire('advagg_flush_caches')) { + return; + } + + // Only run code below if the advagg db tables exist. + if (!db_table_exists('advagg_files')) { + return array('cache_advagg_bundle_reuse'); + } + + // Find files that have changed. + $needs_refreshing = array(); + $results = db_query("SELECT * FROM {advagg_files}"); + while ($row = db_fetch_array($results)) { + $checksum = advagg_checksum($row['filename']); + // Let other modules see if the bundles needs to be rebuilt. + // hook_advagg_files_table + // Return TRUE in order to increment the counter. + $hook_results = module_invoke_all('advagg_files_table', $row, $checksum); + + // Check each return value; see if an update is needed. + $update = FALSE; + if (!empty($hook_results)) { + foreach ($hook_results as $update) { + if ($update === TRUE) { + break; + } + } + } + + // Increment the counter if needed and mark file for bundle refreshment. + if ($checksum != $row['checksum'] || $update == TRUE) { + $needs_refreshing[$row['filename_md5']] = $row['filename']; + // Update checksum; increment counter. + db_query("UPDATE {advagg_files} SET checksum = '%s', counter = counter + 1 WHERE filename_md5 = '%s'", $checksum, $row['filename_md5']); + } + } + + // Get the bundles. + $bundles = array(); + foreach ($needs_refreshing as $filename_md5 => $filename) { + $results = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s'", $filename_md5); + while ($row = db_fetch_array($results)) { + $bundles[$row['bundle_md5']] = $row['bundle_md5']; + } + } + + foreach ($bundles as $bundle_md5) { + // Increment Counter + db_query("UPDATE {advagg_bundles} SET counter = counter + 1, timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5); + + if (variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH)) { + // Rebuild bundles on shutdown in the background. This is needed so that + // the cache_advagg_bundle_reuse table has been cleared. + register_shutdown_function('advagg_rebuild_bundle', $bundle_md5, '', TRUE); + } + } + $_advagg['bundles'] = $bundles; + $_advagg['files'] = $needs_refreshing; + + // Garbage collection + list($css_path, $js_path) = advagg_get_root_files_dir(); + file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE); + file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE); + + lock_release('advagg_flush_caches'); + return array('cache_advagg_bundle_reuse'); +} + +/** + * Rebuild a bundle. + * + * @param $bundle_md5 + * Bundle's machine name. + * @param $counter + * Counter value. + * @param $force + * Rebuild even if file already exists. + */ +function advagg_rebuild_bundle($bundle_md5, $counter = '', $force = FALSE) { + global $conf, $_advagg; + list($filetype, $files) = advagg_get_files_in_bundle($bundle_md5); + + $conf['advagg_async_generation'] = FALSE; + $good = advagg_css_js_file_builder($filetype, $files, '', $counter, $force, $bundle_md5); + if (!$good) { + watchdog('advagg', 'This bundle could not be generated correctly. Bundle MD5: %md5', array('%md5' => $bundle_md5)); + } + else { + $_advagg['rebuilt'][] = $bundle_md5; + } + return $good; +} + +/** + * Get list of files and the filetype given a bundle md5. + * + * @param $bundle_md5 + * Bundle's machine name. + * @return + * array ($filetype, $files) + */ +function advagg_get_files_in_bundle($bundle_md5) { + $files = array(); + $filetype = NULL; + $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = '%s' ORDER BY porder ASC", $bundle_md5); + while ($row = db_fetch_array($results)) { + $files[] = $row['filename']; + $filetype = $row['filetype']; + } + + return array($filetype, $files); +} + +/** + * Callback to delete files modified more than a set time ago. + * + * @param $filename + * name of a file to check how old it is. + */ +function advagg_delete_file_if_stale($filename) { + // Do not process .gz files + if (strpos($filename, '.gz') !== FALSE) { + return; + } + $now = time(); + $file_last_mod = variable_get('advagg_stale_file_threshold', ADVAGG_STALE_FILE_THRESHOLD); + $file_last_used = variable_get('advagg_stale_file_last_used_threshold', ADVAGG_STALE_FILE_LAST_USED_THRESHOLD); + + // Default mtime stale file threshold is 6 days. + advagg_clearstatcache(TRUE, $filename); + if ($now - filemtime($filename) <= $file_last_mod) { + return; + } + + // Check to see if this file is still in use. + $data = cache_get($filename, 'cache_advagg'); + if (!empty($data->data)) { + advagg_clearstatcache(TRUE, $filename); + $file_last_a = @fileatime($filename); + $file_last_agz = @fileatime($filename . '.gz'); + $file_last_a = max($file_last_a, $file_last_agz); + if ($now - $data->data > $file_last_used && $now - $file_last_a > $file_last_used) { + // Delete file if it hasn't been used in the last 3 days. + file_delete($filename); + file_delete($filename . '.gz'); + } + else { + // Touch file so we don't check again for another 6 days. + touch($filename); + } + } + else { + // Delete file if it is not in the cache. + file_delete($filename); + file_delete($filename . '.gz'); + } +} + +/** + * Get data about a file. + * + * @param $filename_md5 + * md5 of filename. + * @return + * data array from database. + */ +function advagg_get_file_data($filename_md5) { + $data = cache_get($filename_md5, 'cache_advagg_files_data'); + if (empty($data->data)) { + return FALSE; + } + return $data->data; +} + +/** + * Set data about a file. + * + * @param $filename_md5 + * md5 of filename. + * @param $data + * data to store. + */ +function advagg_set_file_data($filename_md5, $data) { + cache_set($filename_md5, $data, 'cache_advagg_files_data', CACHE_PERMANENT); +} + +/** + * Given path output uri to that file + * + * @param $filename_md5 + * md5 of filename. + * @param $data + * data to store. + */ +function advagg_build_uri($path) { + static $hook_file_url_alter = array(); + + // If the current path is an absolute path, return immediately. + $fragments = parse_url($path); + if (isset($fragments['host'])) { + return $path; + } + + $original_path = $path; + // CDN Support. + if (module_exists('cdn')) { + $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); + if ($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) { + // Alter URL when the file_create_url() patch is not there. + if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE)) { + cdn_file_url_alter($path); + } + // Use the patched version of file_create_url(). + else { + $path = file_create_url($path); + } + // Return here if the path was changed above. + if (strcmp($original_path, $path) != 0) { + return $path; + } + } + } + + // Other modules besides CDN might want to use hook_file_url_alter. + if (empty($hook_file_url_alter)) { + $hook_file_url_alter = module_implements('file_url_alter'); + } + if (!empty($hook_file_url_alter)) { + $path = file_create_url($path); + // Return here if the path was changed above. + if (strcmp($original_path, $path) != 0) { + return $path; + } + } + + // If nothing was altered then use the drupal default. + return base_path() . $path; +} + +/** + * ***MODIFIED CORE FUNCTIONS BELOW*** + * + * @see drupal_get_css() + * @see drupal_build_css_cache() + * @see drupal_get_js() + * @see drupal_build_js_cache() + */ + +/** + * Returns an array of values needed for aggregation + * + * @param $noagg + * (optional) Bool indicating that aggregation should be disabled if TRUE. + * @param $type + * (optional) String. js or css. + * @return + * array of values to be imported via list() function. + */ +function advagg_process_css_js_prep($noagg = FALSE, $type = NULL) { + global $conf; + $preprocess = (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'); + + // URL bypass. + if ($noagg || (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation'))) { + $preprocess = FALSE; + $conf['advagg_use_full_cache'] = FALSE; + } + + // Cookie bypass. + $cookie_name = 'AdvAggDisabled'; + $key = md5(drupal_get_private_key()); + if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { + $preprocess = FALSE; + $conf['advagg_use_full_cache'] = FALSE; + } + + $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + if (!$public_downloads) { + $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); + if (!empty($custom_path)) { + $public_downloads = TRUE; + } + } + + if ($preprocess) { + if ($type == 'js' && !variable_get('advagg_preprocess_js', ADVAGG_PREPROCESS_JS)) { + $preprocess = FALSE; + } + if ($type == 'css' && !variable_get('advagg_preprocess_css', ADVAGG_PREPROCESS_CSS)) { + $preprocess = FALSE; + } + } + + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. + $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1); + + return array($preprocess, $public_downloads, $query_string); +} + + +/** + * Returns a themed representation of all stylesheets that should be attached to + * the page. + * + * @see drupal_get_css() + * + * It loads the CSS in order, with 'module' first, then 'theme' afterwards. + * This ensures proper cascading of styles so themes can easily override + * module styles through CSS selectors. + * + * Themes may replace module-defined CSS files by adding a stylesheet with the + * same filename. For example, themes/garland/system-menus.css would replace + * modules/system/system-menus.css. This allows themes to override complete + * CSS files, rather than specific selectors, when necessary. + * + * If the original CSS file is being overridden by a theme, the theme is + * responsible for supplying an accompanying RTL CSS file to replace the + * module's. + * + * @param $css + * (optional) An array of CSS files. If no array is provided, the default + * stylesheets array is used instead. + * @param $noagg + * (optional) Bool indicating that aggregation should be disabled if TRUE. + * @return + * A string of XHTML CSS tags. + */ +function advagg_process_css($css = NULL, $noagg = FALSE) { + global $conf; + $original_css = $css; + if (!isset($css)) { + $css = drupal_add_css(); + } + if (empty($css)) { + return FALSE; + } + + // Get useful info. + list($preprocess_css, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg, 'css'); + + // Invoke hook_advagg_css_pre_alter() to give installed modules a chance to + // modify the data in the $javascript array if necessary. + drupal_alter('advagg_css_pre', $css, $preprocess_css, $public_downloads); + + // Set variables. + $external_no_preprocess = array(); + $module_no_preprocess = array(); + $output_no_preprocess = array(); + $output_preprocess = array(); + $theme_no_preprocess = array(); + $inline_no_preprocess = array(); + $files_included = array(); + $files_aggregates_included = array(); + $inline_included = array(); + + // Process input. + foreach ($css as $media => $types) { + // Setup some variables + $files_included[$media] = array(); + $files_aggregates_included[$media] = array(); + $inline_included[$media] = array(); + + // If CSS preprocessing is off, we still need to output the styles. + // Additionally, go through any remaining styles if CSS preprocessing is on + // and output the non-cached ones. + foreach ($types as $type => $files) { + if ($type == 'module') { + // Setup theme overrides for module styles. + $theme_styles = array(); + foreach (array_keys($css[$media]['theme']) as $theme_style) { + $theme_styles[] = basename($theme_style); + } + } + foreach ($types[$type] as $file => $preprocess) { + // If the theme supplies its own style using the name of the module + // style, skip its inclusion. This includes any RTL styles associated + // with its main LTR counterpart. + if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) { + // Unset the file to prevent its inclusion when CSS aggregation is enabled. + unset($types[$type][$file]); + continue; + } + // If a CSS file is not to be preprocessed and it's an inline CSS blob + // it needs to *always* appear at the *very bottom*. + if ($type == 'inline') { + if (is_array($preprocess)) { + foreach ($preprocess as $suffix => $blob) { + $blob = advagg_drupal_load_stylesheet_content($blob, $preprocess); + // Invoke hook_advagg_css_inline_alter() to give installed modules + // a chance to modify the contents of $blob if necessary. + drupal_alter('advagg_css_inline', $blob); + + $inline_no_preprocess[] = array( + 'media' => $media, + 'data' => $blob, + 'prefix' => $file, + 'suffix' => $suffix, + ); + $inline_included[$media][] = $blob; + } + } + // Unset to prevent its inclusion. + unset($types[$type][$file]); + continue; + } + + $prefix = ''; + $suffix = ''; + // Invoke hook_advagg_css_extra_alter() to + // give installed modules a chance to modify the prefix or suffix for a + // given filename. + $values = array($file, NULL, $prefix, $suffix); + drupal_alter('advagg_css_extra', $values); + list($file, $null, $prefix, $suffix) = $values; + + if ($type == 'inline') { + $file = advagg_drupal_load_stylesheet_content($file, $preprocess); + // Invoke hook_advagg_css_inline_alter() to give installed modules a + // chance to modify the contents of $file if necessary. + drupal_alter('advagg_css_inline', $file); + + $inline_no_preprocess[] = array( + 'media' => $media, + 'data' => $file, + 'prefix' => $prefix, + 'suffix' => $suffix, + ); + $inline_included[$media][] = $file; + // Unset to prevent its inclusion. + unset($types[$type][$file]); + continue; + } + + // If a CSS file is not to be preprocessed and it's an external + // CSS file, it needs to *always* appear at the *very top*, + // regardless of whether preprocessing is on or off. + if ($type == 'external') { + $external_no_preprocess[] = array( + 'media' => $media, + 'href' => $file, + 'prefix' => $prefix, + 'suffix' => $suffix, + ); + $files_included[$media][$file] = TRUE; + // Unset the file to prevent its inclusion. + unset($types[$type][$file]); + continue; + } + + // Only include the stylesheet if it exists. + if (advagg_file_exists($file)) { + if (!$preprocess || !($public_downloads && $preprocess_css)) { + + + // Create URI for file. + $file_uri = advagg_build_uri($file) . $query_string; + $files_included[$media][$file] = $preprocess; + // If a CSS file is not to be preprocessed and it's a module CSS + // file, it needs to *always* appear at the *top*, regardless of + // whether preprocessing is on or off. + if (!$preprocess && $type == 'module') { + $module_no_preprocess[] = array( + 'media' => $media, + 'href' => $file_uri, + 'prefix' => $prefix, + 'suffix' => $suffix, + ); + } + // If a CSS file is not to be preprocessed and it's a theme CSS + // file, it needs to *always* appear at the *bottom*, regardless of + // whether preprocessing is on or off. + elseif (!$preprocess && $type == 'theme') { + $theme_no_preprocess[] = array( + 'media' => $media, + 'href' => $file_uri, + 'prefix' => $prefix, + 'suffix' => $suffix, + ); + } + + else { + $output_no_preprocess[] = array( + 'media' => $media, + 'href' => $file_uri, + 'prefix' => $prefix, + 'suffix' => $suffix, + ); + } + } + } + } + } + + if ($public_downloads && $preprocess_css) { + $files_aggregates_included[$media] = $files_included[$media]; + $files = array(); + foreach ($types as $type) { + foreach ($type as $file => $cache) { + if ($cache) { + $files[] = $file; + $files_included[$media][$file] = TRUE; + unset($files_aggregates_included[$file]); + } + } + } + if (!empty($files)) { + $preprocess_files = advagg_css_js_file_builder('css', $files, $query_string); + if (!empty($preprocess_files)) { + $good = TRUE; + foreach ($preprocess_files as $preprocess_file => $extra) { + // Empty aggregate, skip + if (empty($preprocess_file)) { + continue; + } + + if ($extra !== FALSE && is_array($extra)) { + $prefix = $extra['prefix']; + $suffix = $extra['suffix']; + $output_preprocess[] = array( + 'media' => $media, + 'href' => advagg_build_uri($preprocess_file), + 'prefix' => $prefix, + 'suffix' => $suffix, + ); + $files_aggregates_included[$media][$preprocess_file] = $extra; + } + else { + $good = FALSE; + break; + } + } + } + if (empty($good)) { + // Redo with aggregation turned off and return the new value. + watchdog('advagg', 'CSS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR); + $data = advagg_process_css($original_css, TRUE); + return $data; + } + } + } + } + + // Default function called: advagg_unlimited_css_builder + $function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION); + return $function($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included); +} + +/** + * Logic to figure out what kind of css tags to use. + * + * @param $external_no_preprocess + * array of css files ($media, $href, $prefix, $suffix) + * @param $module_no_preprocess + * array of css files ($media, $href, $prefix, $suffix) + * @param $output_no_preprocess + * array of css files ($media, $href, $prefix, $suffix) + * @param $output_preprocess + * array of css files ($media, $href, $prefix, $suffix) + * @param $theme_no_preprocess + * array of css files ($media, $href, $prefix, $suffix) + * @param $inline_no_preprocess + * array of css data to inline ($media, $data) + * @param $inline_included + * array of inline css included. $a[$media][] = $datablob; + * @param $files_included + * array of css files included. $a[$media][] = $filename + * @param $files_aggregates_included + * array of css files & aggregates included. $a[$media][] = $filename + * @return + * html for loading the css. html for the head. + */ +function advagg_unlimited_css_builder($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $files_included, $files_aggregates_included, $inline_included) { + global $user; + $styles = ''; + $files = array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess); + + // Select method for css html output + if (count($files) < variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD)) { + advagg_unlimited_css_traditional($files, $styles); + } + elseif (variable_get('advagg_css_logged_in_ie_detect', ADVAGG_CSS_LOGGED_IN_IE_DETECT) && $user->uid != 0) { + // Detect IE browsers here + $is_ie = FALSE; + if (isset($_SERVER['HTTP_USER_AGENT'])) { + // Strings for testing found via + // http://chrisschuld.com/projects/browser-php-detecting-a-users-browser-from-php/ + // Test for v1 - v1.5 IE + // Test for versions > 1.5 + // Test for Pocket IE + if ( stristr($_SERVER['HTTP_USER_AGENT'], 'microsoft internet explorer') + || stristr($_SERVER['HTTP_USER_AGENT'], 'msie') + || stristr($_SERVER['HTTP_USER_AGENT'], 'mspie') + ) { + $is_ie = TRUE; + } + } + // Play Safe and treat as IE if user agent is not set + else { + $is_ie = TRUE; + } + + if ($is_ie) { + advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles); + advagg_unlimited_css_import($output_preprocess, $styles); + advagg_unlimited_css_import($theme_no_preprocess, $styles); + advagg_unlimited_css_traditional($inline_no_preprocess, $styles); + } + else { + advagg_unlimited_css_traditional($files, $styles); + } + } + else { + advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles); + advagg_unlimited_css_import($output_preprocess, $styles); + advagg_unlimited_css_import($theme_no_preprocess, $styles); + advagg_unlimited_css_traditional($inline_no_preprocess, $styles); + } + + return $styles; +} + +/** + * Use link tags for CSS + * + * @param $files + * array of css files ($media, $href, $prefix, $suffix) + * @param &$styles + * html string + */ +function advagg_unlimited_css_traditional($files, &$styles) { + $last_prefix = ''; + $last_suffix = ''; + foreach ($files as $css_file) { + $media = $css_file['media']; + $prefix = empty($css_file['prefix']) ? '' : $css_file['prefix'] . "\n"; + $suffix = empty($css_file['suffix']) ? '' : $css_file['suffix']; + + // Group prefixes and suffixes. + if (isset($css_file['href'])) { + $href = $css_file['href']; + if ($prefix != $last_prefix) { + $styles .= $last_suffix . "\n" . $prefix . '' . "\n"; + } + else { + $styles .= '' . "\n"; + } + } + else { + $data = $css_file['data']; + if ($prefix != $last_prefix) { + $styles .= $last_suffix . "\n" . $prefix . '' . "\n"; + } + else { + $styles .= '' . "\n"; + } + } + $last_prefix = $prefix; + $last_suffix = $suffix; + } + $styles .= empty($last_suffix) ? '' : $last_suffix . "\n"; +} + +/** + * Use import tags for CSS + * + * @param $files + * array of css files ($media, $href) + * @param &$styles + * html string + */ +function advagg_unlimited_css_import($files, &$styles) { + $counter = 0; + $media = NULL; + $import = ''; + foreach ($files as $css_file) { + $media_new = $css_file['media']; + $href = $css_file['href']; + if ($media_new != $media || $counter > variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD)) { + if ($media && !empty($import)) { + $styles .= "\n" . ''; + $import = ''; + } + $counter = 0; + $media = $media_new; + } + $import .= '@import "' . $href . '";' . "\n"; + $counter++; + } + if ($media && !empty($import)) { + $styles .= "\n" . ''; + } +} + +/** + * Returns a themed presentation of all JavaScript code for the current page. + * + * @see drupal_get_js() + * + * References to JavaScript files are placed in a certain order: first, all + * 'core' files, then all 'module' and finally all 'theme' JavaScript files + * are added to the page. Then, all settings are output, followed by 'inline' + * JavaScript code. If running update.php, all preprocessing is disabled. + * + * @param $js_code + * An array with all JavaScript code. Key it the region + * @param $noagg + * (optional) Bool indicating that aggregation should be disabled if TRUE. + * @return + * All JavaScript code segments and includes for the scope as HTML tags. + */ +function advagg_process_js($master_set, $noagg = FALSE) { + global $conf; + if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) { + locale_update_js_files(); + } + + // Get useful info. + list($preprocess_js, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg, 'js'); + $advagg_json_encode_function = variable_get('advagg_json_encode_function', 'advagg_drupal_to_js'); + + $output = array(); + foreach ($master_set as $scope => $javascript) { + if ($scope != 'header' && $scope != 'footer' && empty($javascript)) { + continue; + } + + // Invoke hook_advagg_js_pre_alter() to give installed modules a chance to + // modify the data in the $javascript array if necessary. + drupal_alter('advagg_js_pre', $javascript, $preprocess_js, $public_downloads, $scope); + $master_set[$scope] = $javascript; + } + + // Invoke hook_advagg_js_header_footer_alter() to give installed modules a chance to + // modify the data in the header and footer JS if necessary. + drupal_alter('advagg_js_header_footer', $master_set, $preprocess_js, $public_downloads); + + foreach ($master_set as $scope => $javascript) { + if (empty($javascript)) { + continue; + } + + // Set variables. + $setting_no_preprocess = array(); + $inline_no_preprocess = array(); + $external_no_preprocess = array(); + $output_no_preprocess = array( + 'core' => array(), + 'module' => array(), + 'theme' => array(), + ); + $output_preprocess = array(); + $preprocess_list = array(); + $js_settings_array = array(); + $inline_included = array(); + $files_included = array(); + $files_aggregates_included = array(); + + // Process input. + foreach ($javascript as $type => $data) { + if (empty($data)) { + continue; + } + + // Add the prefix and suffix to the info array if not there. + if ($type != 'setting') { + foreach ($data as &$info) { + $info['prefix'] = isset($info['prefix']) ? $info['prefix'] : ''; + $info['suffix'] = isset($info['suffix']) ? $info['suffix'] : ''; + } + // $info needs to be unset, otherwise foreach loops below will break. + unset($info); + } + + switch ($type) { + case 'setting': + $data = call_user_func_array('array_merge_recursive', $data); + $js_settings_array[] = $data; + $js_settings = $advagg_json_encode_function($data); + $js_settings = preg_replace(array('/"DRUPAL_JS_RAW\:/', '/\:DRUPAL_JS_RAW"/'), array('', ''), $js_settings); + $setting_no_preprocess[] = 'jQuery.extend(Drupal.settings, ' . $js_settings . ");"; + break; + + case 'inline': + foreach ($data as $info) { + // Invoke hook_advagg_js_inline_alter() to give installed modules a + // chance to modify the contents of $info['code'] if necessary. + drupal_alter('advagg_js_inline', $info['code']); + + $inline_no_preprocess[] = array($info['code'], $info['defer'], $info['prefix'], $info['suffix']); + $inline_included[] = $info['code']; + } + break; + + case 'external': + foreach ($data as $path => $info) { + $external_no_preprocess[] = array($path, $info['defer'], $info['prefix'], $info['suffix']); + $files_included[$path] = TRUE; + } + break; + + default: + // If JS preprocessing is off, we still need to output the scripts. + // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. + foreach ($data as $path => $info) { + if (!$info['preprocess'] || !$public_downloads || !$preprocess_js) { + $output_no_preprocess[$type][] = array(advagg_build_uri($path) . ($info['cache'] ? $query_string : '?' . time()), $info['defer'], $info['prefix'], $info['suffix']); + $files_included[$path] = $info['preprocess']; + } + else { + $preprocess_list[$path] = $info; + } + } + break; + + } + } + + // Aggregate any remaining JS files that haven't already been output. + if ($public_downloads && $preprocess_js && count($preprocess_list) > 0) { + $files_aggregates_included = $files_included; + $files = array(); + foreach ($preprocess_list as $path => $info) { + if ($info['preprocess']) { + $files[] = $path; + $files_included[$path] = TRUE; + } + } + if (!empty($files)) { + $preprocess_files = advagg_css_js_file_builder('js', $files, $query_string); + if (!empty($preprocess_files)) { + $good = TRUE; + foreach ($preprocess_files as $preprocess_file => $extra) { + // Empty aggregate, skip + if (empty($preprocess_file)) { + continue; + } + + if ($extra !== FALSE && is_array($extra)) { + $prefix = $extra['prefix']; + $suffix = $extra['suffix']; + $output_preprocess[] = array(advagg_build_uri($preprocess_file), $prefix, $suffix); + $files_aggregates_included[$preprocess_file] = $extra; + } + else { + $good = FALSE; + break; + } + } + } + if (empty($good)) { + // Redo with aggregation turned off and return the new value. + watchdog('advagg', 'JS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR); + $data = advagg_process_js($master_set, TRUE); + return $data; + } + } + } + + // Default function called: advagg_js_builder + $function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION); + $output[$scope] = $function($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included); + } + return $output; +} + +/** + * Build and theme JS output for header. + * + * @param $external_no_preprocess + * array(array($src, $defer, $prefix, $suffix)) + * @param $output_preprocess + * array(array($src, $prefix, $suffix)) + * @param $output_no_preprocess + * array(array(array($src, $defer, $prefix, $suffix))) + * @param $setting_no_preprocess + * array(array($code)) + * @param $inline_no_preprocess + * array(array($code, $defer, $prefix, $suffix)) + * @param $scope + * header or footer. + * @param $js_settings_array + * array of settings used. + * @param $inline_included + * array of inline scripts used. + * @param $files_included + * array of files used. + * @param $files_aggregates_included + * array of files and aggregates used. + * @return + * String of themed JavaScript. + */ +function advagg_js_builder($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) { + $output = ''; + + // For inline Javascript to validate as XHTML, all Javascript containing + // XHTML needs to be wrapped in CDATA. To make that backwards compatible + // with HTML 4, we need to comment out the CDATA-tag. + $embed_prefix = "\n\n"; + + // Keep the order of JS files consistent as some are preprocessed and others are not. + // Make sure any inline or JS setting variables appear last after libraries have loaded. + + if (!empty($external_no_preprocess)) { + foreach ($external_no_preprocess as $values) { + list($src, $defer, $prefix, $suffix) = $values; + $output .= $prefix . '' . $suffix . "\n"; + } + } + + if (!empty($output_preprocess)) { + foreach ($output_preprocess as $values) { + list($src, $prefix, $suffix) = $values; + $output .= $prefix . '' . $suffix . "\n"; + } + } + + foreach ($output_no_preprocess as $type => $list) { + if (!empty($list)) { + foreach ($list as $values) { + list($src, $defer, $prefix, $suffix) = $values; + $output .= $prefix . '' . $suffix . "\n"; + } + } + } + + if (!empty($setting_no_preprocess)) { + foreach ($setting_no_preprocess as $code) { + $output .= '\n"; + } + } + + if (!empty($inline_no_preprocess)) { + foreach ($inline_no_preprocess as $values) { + list($code, $defer, $prefix, $suffix) = $values; + $output .= $prefix . '' . $suffix . "\n"; + } + } + + return $output; +} + +/** + * Always return TRUE, used for array_map in advagg_css_js_file_builder(). + */ +function advagg_return_true() { + return TRUE; +} + +/** + * Disable the page cache if the aggregate is not in the bundle. + */ +function advagg_disable_page_cache() { + global $conf; + $conf['advagg_use_full_cache'] = FALSE; + if (variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE)) { + $conf['cache'] = CACHE_DISABLED; + + // Invoke hook_advagg_disable_page_cache(). Allows 3rd party page cache + // plugins like boost or varnish to not cache this page. + module_invoke_all('advagg_disable_page_cache'); + } +} + +/** + * Aggregate CSS/JS files, putting them in the files directory. + * + * @see drupal_build_js_cache() + * @see drupal_build_css_cache() + * + * @param $type + * js or css + * @param $files + * An array of JS files to aggregate and compress into one file. + * @param $query_string + * (optional) Query string to add on to the file if bundle isn't ready. + * @param $counter + * (optional) Counter value. + * @param $force + * (optional) Rebuild even if file already exists. + * @param $md5 + * (optional) Bundle's machine name. + * @return + * array with the filepath as the key and prefix and suffix in another array. + */ +function advagg_css_js_file_builder($type, $files, $query_string = '', $counter = FALSE, $force = FALSE, $md5 = '') { + global $_advagg, $base_path; + $data = ''; + + // Try cache first. When ever the counter changes this cache gets reset. + $schema = advagg_get_server_schema(); + $cached_data_key = 'advagg_file_builder_' . md5($schema . implode('', array_filter(array_unique($files)))); + if (!$force) { + // Try cache first; cache table is cache_advagg_bundle_reuse. + $cached_data = advagg_cached_bundle_get($cached_data_key, 'file_builder_cache_object'); + if (!empty($cached_data)) { + foreach ($cached_data as $filepath => $values) { + // Ping cache. + advagg_bundle_built($filepath); + } + return $cached_data; + } + } + + list($css_path, $js_path) = advagg_get_root_files_dir(); + if ($type == 'js') { + $file_type_path = $js_path; + } + if ($type == 'css') { + $file_type_path = $css_path; + } + + // Send $files, get filename back + $filenames = advagg_get_filename($files, $type, $counter, $md5); + if (empty($filenames)) { + return FALSE; + } + + // Debugging. + if (variable_get('advagg_debug', ADVAGG_DEBUG)) { + $_advagg['debug']['file_builder_get_filenames'][] = array( + 'key' => $cached_data_key, + 'filenames' => $filenames, + ); + } + $output = array(); + $locks = array(); + $cacheable = TRUE; + $files_used = array(); + foreach ($filenames as $info) { + $filename = $info['filename']; + $files = $info['files']; + $bundle_md5 = $info['bundle_md5']; + $prefix = ''; + $suffix = ''; + $filepath = $file_type_path . '/' . $filename; + + // Invoke hook_advagg_js_extra_alter() or hook_advagg_css_extra_alter to + // give installed modules a chance to modify the prefix or suffix for a + // given filename. + $values = array($filename, $bundle_md5, $prefix, $suffix); + drupal_alter('advagg_' . $type . '_extra', $values); + list($filename, $bundle_md5, $prefix, $suffix) = $values; + + // Check that the file exists & filesize is not zero + $built = advagg_bundle_built($filepath); + + if (!$built || $force) { + // Generate on request? + if (variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION) && !$force && empty($_GET['generator'])) { + // Build request. + $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0; + $url = _advagg_build_url($filepath . '?generator=1&redirect_counter=' . $redirect_counter); + $headers = array( + 'Host' => $_SERVER['HTTP_HOST'], + 'Connection' => 'close', + ); + + // Request file. + if (function_exists('stream_socket_client') && function_exists('stream_select')) { + advagg_async_connect_http_request($url, array('headers' => $headers)); + } + else { + // Set timeout. + $socket_timeout = ini_set('default_socket_timeout', variable_get('advagg_socket_timeout', ADVAGG_SOCKET_TIMEOUT)); + drupal_http_request($url, $headers, 'GET'); + ini_set('default_socket_timeout', $socket_timeout); + } + + // Return filepath if we are going to wait for the bundle to be + // generated or if the bundle already exists. + if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2 || advagg_bundle_built($filepath)) { + $output[$filepath] = array( + 'prefix' => $prefix, + 'suffix' => $suffix, + 'files' => array_map('advagg_return_true', array_flip($files)), + ); + } + else { + // Aggregate isn't built yet, send back the files that where going to + // be in it. + foreach ($files as $file) { + $output[$file . $query_string] = array( + 'prefix' => '', + 'suffix' => '', + 'files' => array($file . $query_string => TRUE), + ); + } + $cacheable = FALSE; + advagg_disable_page_cache(); + } + continue; + } + + // Only generate once. + $lock_name = 'advagg_' . $filename; + if (!lock_acquire($lock_name)) { + if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) == 0 ) { + $locks[] = array($lock_name => $filepath); + $output[$filepath] = array( + 'prefix' => $prefix, + 'suffix' => $suffix, + 'files' => array_map('advagg_return_true', array_flip($files)), + ); + } + else { + // Aggregate isn't built yet, send back the files that where going + // to be in it. + foreach ($files as $file) { + $output[$file . $query_string] = array( + 'prefix' => '', + 'suffix' => '', + 'files' => array($file . $query_string => TRUE), + ); + } + $cacheable = FALSE; + advagg_disable_page_cache(); + } + continue; + } + + if ($type == 'css') { + $data = advagg_build_css_bundle($files); + } + elseif ($type == 'js') { + $data = advagg_build_js_bundle($files); + } + + // Invoke hook_advagg_js_alter() or hook_advagg_css_alter to give + // installed modules a chance to modify the data in the bundle if + // necessary. + drupal_alter('advagg_' . $type, $data, $files, $bundle_md5); + $files_used = array_merge($files_used, $files); + + // If data is empty then do not include this bundle in the final output. + if (empty($data) && !$force) { + lock_release($lock_name); + continue; + } + + // Create the advagg_$type/ within the files folder. + file_check_directory($file_type_path, FILE_CREATE_DIRECTORY); + + // Write file. default function called: advagg_file_saver + $function = variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION); + $good = $function($data, $filepath, $force, $type); + + // Release lock. + lock_release($lock_name); + + // If file save was not good then downgrade to non aggregated mode. + if (!$good) { + $output[$filepath] = FALSE; + $cacheable = FALSE; + continue; + } + } + else { + $files_used = array_merge($files_used, $files); + } + $output[$filepath] = array( + 'prefix' => $prefix, + 'suffix' => $suffix, + 'files' => array_map('advagg_return_true', array_flip($files)), + ); + } + + // Wait for all locks before returning. + if (!empty($locks)) { + foreach ($locks as $lock_name => $filepath) { + lock_wait($lock_name); + if (!advagg_bundle_built($filepath)) { + $output[$filepath] = FALSE; + } + } + } + + if (empty($output)) { + $output[] = FALSE; + return $output; + } + + // Cache the output + if (!$force && $cacheable) { + $new_cached_data_key = 'advagg_file_builder_' . md5($schema . implode('', array_filter(array_unique($files_used)))); + // Verify the files in equals the files out. + if ($new_cached_data_key == $cached_data_key) { + cache_set($cached_data_key, $output, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + } + } + + return $output; +} + +/** + * Given a list of files, grab their contents and glue it into one big string. + * + * @param $files + * array of filenames. + * @return + * string containing all the files. + */ +function advagg_build_css_bundle($files) { + $data = ''; + // Build aggregate CSS file. + foreach ($files as $file) { + $contents = drupal_load_stylesheet($file, TRUE); + // Return the path to where this CSS file originated from. + $base = base_path() . dirname($file) . '/'; + _drupal_build_css_path(NULL, $base); + // Prefix all paths within this CSS file, ignoring external and absolute paths. + $data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents); + } + + // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, + // @import rules must proceed any other style, so we move those to the top. + $regexp = '/@import[^;]+;/i'; + preg_match_all($regexp, $data, $matches); + $data = preg_replace($regexp, '', $data); + $data = implode('', $matches[0]) . $data; + return $data; +} + +/** + * Given a list of files, grab their contents and glue it into one big string. + * + * @param $files + * array of filenames. + * @return + * string containing all the files. + */ +function advagg_build_js_bundle($files) { + if (empty($files)) { + return ''; + } + $data = ''; + // Build aggregate JS file. + foreach ($files as $file) { + // Append a ';' and a newline after each JS file to prevent them from running together. + if (advagg_file_exists($file)) { + $data .= file_get_contents($file) . ";\n"; + } + } + return $data; +} + +/** + * Use a cache table to see if a file exists. + * + * @param $filename + * name of file + * @return + * TRUE or FALSE + */ +function advagg_file_exists($filename) { + static $files = array(); + if (empty($files)) { + $data = cache_get('advagg_file_checksum', 'cache'); + if (empty($data->data)) { + $result = db_query("SELECT filename, checksum FROM {advagg_files}"); + while ($row = db_fetch_array($result)) { + $files[$row['filename']] = $row['checksum']; + } + cache_set('advagg_file_checksum', $files, 'cache', CACHE_TEMPORARY); + } + else { + $files = $data->data; + } + } + if (!empty($files[$filename]) && $files[$filename] != -1) { + return TRUE; + } + else { + advagg_clearstatcache(TRUE, $filename); + return file_exists($filename); + } +} + +/** + * Send out a fast 404 and exit. + */ +function advagg_missing_fast404($msg = '') { + global $base_path; + if (!headers_sent()) { + header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); + header('X-AdvAgg: Failed Validation. ' . $msg); + } + + print '' . "\n"; + print ''; + print '404 Not Found'; + print '

Not Found

'; + print '

The requested URL was not found on this server.

'; + print '

Home

'; + print ''; + print ''; + exit(); +} + +/** + * Generate .htaccess rules and place them in advagg dir + * + * @param $dest + * destination of the file that just got saved. + * @param $force + * force recreate the .htaccess file. + */ +function advagg_htaccess_check_generate($dest, $force = FALSE) { + global $base_path; + if (!$force && !variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS)) { + return TRUE; + } + + $dir = dirname($dest); + $htaccess_file = $dir . '/.htaccess'; + advagg_clearstatcache(TRUE, $htaccess_file); + if (!$force && file_exists($htaccess_file)) { + return TRUE; + } + + list($css_path, $js_path) = advagg_get_root_files_dir(); + + $type = ''; + if ($dir == $js_path) { + $ext = 'js'; + $path = $js_path; + $type = 'text/javascript'; + } + elseif ($dir == $css_path) { + $ext = 'css'; + $path = $css_path; + $type = 'text/css'; + } + else { + return FALSE; + } + + $data = "\n"; + if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION)) { + $data .= "\n"; + $data .= " RewriteEngine on\n"; + $data .= " RewriteBase ${base_path}${path}\n"; + $data .= "\n"; + $data .= " # Send 404's back to index.php\n"; + $data .= " RewriteCond %{REQUEST_FILENAME} !-s\n"; + $data .= " RewriteRule ^(.*)$ ${base_path}index.php?q=$path/$1 [L]\n"; + $data .= "\n"; + $data .= " # Rules to correctly serve gzip compressed $ext files.\n"; + $data .= " # Requires both mod_rewrite and mod_headers to be enabled.\n"; + $data .= " \n"; + $data .= " # Serve gzip compressed $ext files if they exist and client accepts gzip.\n"; + $data .= " RewriteCond %{HTTP:Accept-encoding} gzip\n"; + $data .= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n"; + $data .= " RewriteRule ^(.*)\.$ext$ $1\.$ext\.gz [QSA]\n"; + $data .= "\n"; + $data .= " # Serve correct content types, and prevent mod_deflate double gzip.\n"; + $data .= " RewriteRule \.$ext\.gz$ - [T=$type,E=no-gzip:1]\n"; + $data .= "\n"; + $data .= " \n"; + $data .= " # Serve correct encoding type.\n"; + $data .= " Header set Content-Encoding gzip\n"; + $data .= " # Force proxies to cache gzipped & non-gzipped $ext files separately.\n"; + $data .= " Header append Vary Accept-Encoding\n"; + $data .= " \n"; + $data .= " \n"; + $data .= "\n"; + $data .= "\n"; + } + $data .= "\n"; + $data .= " # No mod_headers\n"; + $data .= " \n"; + $data .= " # No mod_expires\n"; + $data .= " \n"; + $data .= " # Use ETags.\n"; + $data .= " FileETag MTime Size\n"; + $data .= " \n"; + $data .= "\n"; + $data .= " # Use Expires Directive.\n"; + $data .= " \n"; + $data .= " # Do not use ETags.\n"; + $data .= " FileETag None\n"; + $data .= " # Enable expirations.\n"; + $data .= " ExpiresActive On\n"; + $data .= " # Cache all aggregated $ext files for 480 weeks after access (A).\n"; + $data .= " ExpiresDefault A290304000\n"; + $data .= " \n"; + $data .= " \n"; + $data .= "\n"; + $data .= " \n"; + $data .= " # Set a far future Cache-Control header to 480 weeks.\n"; + $data .= " Header set Cache-Control \"max-age=290304000, no-transform, public\"\n"; + $data .= " # Set a far future Expires header.\n"; + $data .= " Header set Expires \"Tue, 20 Jan 2037 04:20:42 GMT\"\n"; + $data .= " # Pretend the file was last modified a long time ago in the past.\n"; + $data .= " Header set Last-Modified \"Wed, 20 Jan 1988 04:20:42 GMT\"\n"; + $data .= " # Do not use etags for cache validation.\n"; + $data .= " Header unset ETag\n"; + $data .= " \n"; + $data .= "\n"; + + if (!advagg_file_save_data($data, $htaccess_file, FILE_EXISTS_REPLACE)) { + return FALSE; + } + return TRUE; +} + +/** + * Adds a CSS file to the stylesheet queue. + * + * @param $data + * (optional) The CSS data that will be set. If not set then the inline CSS + * array will be passed back. + * @param $media + * (optional) The media type for the stylesheet, e.g., all, print, screen. + * @param $prefix + * (optional) prefix to add before the inlined css. + * @param $suffix + * (optional) suffix to add after the inlined css. + * @return + * An array of CSS files. + */ +function advagg_add_css_inline($data = NULL, $media = 'all', $prefix = NULL, $suffix = NULL) { + static $css = array(); + + // Store inline data in a static. + if (isset($data)) { + if (!isset($css[$media]['inline'][$prefix][$suffix])) { + $css[$media]['inline'][$prefix][$suffix] = $data; + } + else { + $css[$media]['inline'][$prefix][$suffix] .= "\n" . $data; + } + return; + } + else { + return $css; + } +} + +/** + * Converts a PHP variable into its Javascript equivalent. + * + * We use HTML-safe strings, i.e. with <, > and & escaped. + */ +function advagg_drupal_to_js($var) { + static $php530; + if (!isset($php530)) { + $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); + } + + // json_encode on PHP prior to PHP 5.3.0 doesn't support options. + if ($php530) { + return json_encode($var, JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS); + } + + // if json_encode exists, use it. + if (function_exists('json_encode')) { + return str_replace(array("<", ">", "&"), array('\u003c', '\u003e', '\u0026'), json_encode($var)); + } + + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; // Lowercase necessary! + case 'integer': + case 'double': + return $var; + case 'resource': + case 'string': + // Always use Unicode escape sequences (\u0022) over JSON escape + // sequences (\") to prevent browsers interpreting these as + // special characters. + $replace_pairs = array( + // ", \ and U+0000 - U+001F must be escaped according to RFC 4627. + '\\' => '\u005C', + '"' => '\u0022', + "\x00" => '\u0000', + "\x01" => '\u0001', + "\x02" => '\u0002', + "\x03" => '\u0003', + "\x04" => '\u0004', + "\x05" => '\u0005', + "\x06" => '\u0006', + "\x07" => '\u0007', + "\x08" => '\u0008', + "\x09" => '\u0009', + "\x0a" => '\u000A', + "\x0b" => '\u000B', + "\x0c" => '\u000C', + "\x0d" => '\u000D', + "\x0e" => '\u000E', + "\x0f" => '\u000F', + "\x10" => '\u0010', + "\x11" => '\u0011', + "\x12" => '\u0012', + "\x13" => '\u0013', + "\x14" => '\u0014', + "\x15" => '\u0015', + "\x16" => '\u0016', + "\x17" => '\u0017', + "\x18" => '\u0018', + "\x19" => '\u0019', + "\x1a" => '\u001A', + "\x1b" => '\u001B', + "\x1c" => '\u001C', + "\x1d" => '\u001D', + "\x1e" => '\u001E', + "\x1f" => '\u001F', + // Prevent browsers from interpreting these as as special. + "'" => '\u0027', + '<' => '\u003C', + '>' => '\u003E', + '&' => '\u0026', + // Prevent browsers from interpreting the solidus as special and + // non-compliant JSON parsers from interpreting // as a comment. + '/' => '\u002F', + // While these are allowed unescaped according to ECMA-262, section + // 15.12.2, they cause problems in some JSON parsers. + "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator. + "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator. + ); + + return '"' . strtr($var, $replace_pairs) . '"'; + case 'array': + // Arrays in JSON can't be associative. If the array is empty or if it + // has sequential whole number keys starting with 0, it's not associative + // so we can go ahead and convert it as an array. + if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) { + $output = array(); + foreach ($var as $v) { + $output[] = advagg_drupal_to_js($v); + } + return '[ ' . implode(', ', $output) . ' ]'; + } + // Otherwise, fall through to convert the array as an object. + case 'object': + $output = array(); + foreach ($var as $k => $v) { + $output[] = advagg_drupal_to_js(strval($k)) . ': ' . advagg_drupal_to_js($v); + } + return '{ ' . implode(', ', $output) . ' }'; + default: + return 'null'; + } +} + +/** + * Process the contents of a stylesheet for aggregation. + * + * @param $contents + * The contents of the stylesheet. + * @param $optimize + * (optional) Boolean whether CSS contents should be minified. Defaults to + * FALSE. + * @return + * Contents of the stylesheet including the imported stylesheets. + */ +function advagg_drupal_load_stylesheet_content($contents, $optimize = FALSE) { + // Remove multiple charset declarations for standards compliance (and fixing Safari problems). + $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents); + + if ($optimize) { + // Perform some safe CSS optimizations. + // Regexp to match comment blocks. + $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + // Regexp to match double quoted strings. + $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + // Regexp to match single quoted strings. + $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; + // Strip all comment blocks, but keep double/single quoted strings. + $contents = preg_replace( + "<($double_quot|$single_quot)|$comment>Ss", + "$1", + $contents + ); + // Remove certain whitespace. + // There are different conditions for removing leading and trailing + // whitespace. + // @see http://php.net/manual/en/regexp.reference.subpatterns.php + $contents = preg_replace('< + # Strip leading and trailing whitespace. + \s*([@{};,])\s* + # Strip only leading whitespace from: + # - Closing parenthesis: Retain "@media (bar) and foo". + | \s+([\)]) + # Strip only trailing whitespace from: + # - Opening parenthesis: Retain "@media (bar) and foo". + # - Colon: Retain :pseudo-selectors. + | ([\(:])\s+ + >xS', + // Only one of the three capturing groups will match, so its reference + // will contain the wanted value and the references for the + // two non-matching groups will be replaced with empty strings. + '$1$2$3', + $contents + ); + // End the file with a new line. + $contents = trim($contents); + $contents .= "\n"; + } + + // Replaces @import commands with the actual stylesheet content. + // This happens recursively but omits external files. + $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_advagg_drupal_load_stylesheet', $contents); + return $contents; +} + +/** + * Loads stylesheets recursively and returns contents with corrected paths. + * + * This function is used for recursive loading of stylesheets and + * returns the stylesheet content with all url() paths corrected. + */ +function _advagg_drupal_load_stylesheet($matches) { + $filename = $matches[1]; + // Load the imported stylesheet and replace @import commands in there as well. + $file = advagg_build_css_bundle(array($filename)); + + // Determine the file's directory. + $directory = dirname($filename); + // If the file is in the current directory, make sure '.' doesn't appear in + // the url() path. + $directory = $directory == '.' ? '' : $directory . '/'; + + // Alter all internal url() paths. Leave external paths alone. We don't need + // to normalize absolute paths here (i.e. remove folder/... segments) because + // that will be done later. + return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1' . $directory, $file); +} + +/** + * Perform an HTTP request; does not wait for reply & you will never get it + * back. + * + * @see drupal_http_request() + * + * This is a flexible and powerful HTTP client implementation. Correctly + * handles GET, POST, PUT or any other HTTP requests. + * + * @param $url + * A string containing a fully qualified URI. + * @param array $options + * (optional) An array that can have one or more of the following elements: + * - headers: An array containing request headers to send as name/value pairs. + * - method: A string containing the request method. Defaults to 'GET'. + * - data: A string containing the request body, formatted as + * 'param=value¶m=value&...'. Defaults to NULL. + * - max_redirects: An integer representing how many times a redirect + * may be followed. Defaults to 3. + * - timeout: A float representing the maximum number of seconds the function + * call may take. The default is 30 seconds. If a timeout occurs, the error + * code is set to the HTTP_REQUEST_TIMEOUT constant. + * - context: A context resource created with stream_context_create(). + * @return bool + * return value from advagg_async_send_http_request(). + */ +function advagg_async_connect_http_request($url, array $options = array()) { + $result = new stdClass(); + + // Parse the URL and make sure we can handle the schema. + $uri = @parse_url($url); + + if (empty($uri)) { + $result->error = 'unable to parse URL'; + $result->code = -1001; + return $result; + } + + if (!isset($uri['scheme'])) { + $result->error = 'missing schema'; + $result->code = -1002; + return $result; + } + + // Merge the default options. + $options += array( + 'headers' => array(), + 'method' => 'GET', + 'data' => NULL, + 'max_redirects' => 3, + 'timeout' => 30.0, + 'context' => NULL, + ); + // stream_socket_client() requires timeout to be a float. + $options['timeout'] = (float) $options['timeout']; + + switch ($uri['scheme']) { + case 'http': + case 'feed': + $port = isset($uri['port']) ? $uri['port'] : 80; + $socket = 'tcp://' . $uri['host'] . ':' . $port; + // RFC 2616: "non-standard ports MUST, default ports MAY be included". + // We don't add the standard port to prevent from breaking rewrite rules + // checking the host that do not take into account the port number. + if (empty($options['headers']['Host'])) { + $options['headers']['Host'] = $uri['host']; + } + if ($port != 80) { + $options['headers']['Host'] .= ':' . $port; + } + break; + case 'https': + // Note: Only works when PHP is compiled with OpenSSL support. + $port = isset($uri['port']) ? $uri['port'] : 443; + $socket = 'ssl://' . $uri['host'] . ':' . $port; + if (empty($options['headers']['Host'])) { + $options['headers']['Host'] = $uri['host']; + } + if ($port != 443) { + $options['headers']['Host'] .= ':' . $port; + } + break; + default: + $result->error = 'invalid schema ' . $uri['scheme']; + $result->code = -1003; + return $result; + } + + $flags = STREAM_CLIENT_CONNECT; + if (variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT)) { + $flags = STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT; + } + if (empty($options['context'])) { + $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags); + } + else { + // Create a stream with context. Allows verification of a SSL certificate. + $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']); + } + + // Make sure the socket opened properly. + if (!$fp) { + // When a network error occurs, we use a negative number so it does not + // clash with the HTTP status codes. + $result->code = -$errno; + $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket)); + + return $result; + } + + // Non blocking stream. + stream_set_blocking($fp, 0); + + // Construct the path to act on. + $path = isset($uri['path']) ? $uri['path'] : '/'; + if (isset($uri['query'])) { + $path .= '?' . $uri['query']; + } + + // Merge the default headers. + $options['headers'] += array( + 'User-Agent' => 'Drupal (+http://drupal.org/)', + ); + + // Only add Content-Length if we actually have any content or if it is a POST + // or PUT request. Some non-standard servers get confused by Content-Length in + // at least HEAD/GET requests, and Squid always requires Content-Length in + // POST/PUT requests. + $content_length = strlen($options['data']); + if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') { + $options['headers']['Content-Length'] = $content_length; + } + + // If the server URL has a user then attempt to use basic authentication. + if (isset($uri['user'])) { + $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : '')); + } + + // If the database prefix is being used by SimpleTest to run the tests in a copied + // database then set the user-agent header to the database prefix so that any + // calls to other Drupal pages will run the SimpleTest prefixed database. The + // user-agent is used to ensure that multiple testing sessions running at the + // same time won't interfere with each other as they would if the database + // prefix were stored statically in a file or database variable. + $test_info = &$GLOBALS['drupal_test_info']; + if (!empty($test_info['test_run_id'])) { + $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']); + } + + $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n"; + foreach ($options['headers'] as $name => $value) { + $request .= $name . ': ' . trim($value) . "\r\n"; + } + $request .= "\r\n" . $options['data']; + $result->request = $request; + + return advagg_async_send_http_request($fp, $request, $options['timeout']); +} + +/** + * Perform an HTTP request; does not wait for reply & you never will get it + * back. + * + * @see drupal_http_request() + * + * This is a flexible and powerful HTTP client implementation. Correctly + * handles GET, POST, PUT or any other HTTP requests. + * + * @param $fp + * (optional) A file pointer. + * @param $request + * (optional) A string containing the request headers to send to the server. + * @param $timeout + * (optional) An integer holding the stream timeout value. + * @return bool + * TRUE if function worked as planed. + */ +function advagg_async_send_http_request($fp = NULL, $request = '', $timeout = 30) { + static $requests = array(); + static $registered = FALSE; + + // Store data in a static, and register a shutdown function. + $args = array($fp, $request, $timeout); + if (!empty($fp)) { + $requests[] = $args; + if (!$registered) { + register_shutdown_function(__FUNCTION__); + $registered = TRUE; + } + return TRUE; + } + + // Shutdown function run. + if (empty($requests)) { + return FALSE; + } + + $streams = array(); + foreach ($requests as $id => $values) { + list($fp, $request, $timeout) = $values; + $streams[$id] = $fp; + } + + $retry_count = 2; + // Run the loop as long as we have a stream to write to. + while (!empty($streams)) { + // Set the read and write vars to the streams var. + $read = $write = $streams; + $except = array(); + + // Do some voodoo and open all streams at once. + $n = @stream_select($read, $write, $except, $timeout); + + // We have some streams to write to. + if (!empty($n)) { + // Write to each stream if it is available. + foreach ($write as $id => $w) { + fwrite($w, $requests[$id][1]); + fclose($w); + unset($streams[$id]); + } + } + // Timed out waiting or all $streams are closed at this point. + elseif (!empty($retry_count)) { + $retry_count--; + } + else { + break; + } + } + // Free memory. + $requests = array(); + if ($n !== FALSE && empty($streams)) { + return TRUE; + } + else { + return FALSE; + } +} + +/** + * Implement hook_advagg_js_header_footer_alter. + */ +function advagg_advagg_js_header_footer_alter(&$master_set, $preprocess_js, $public_downloads) { + // Don't run the code below if ctools ajax is not loaded. + if (!defined('CTOOLS_AJAX_INCLUDED')) { + return; + } + + // Get all JS files set to be loaded. + $js_files = array(); + foreach ($master_set as $scope => $scripts) { + if (empty($scripts)) { + continue; + } + advagg_ctools_process_js_files($js_files, $scope, $scripts); + } + + // Add list of CSS & JS files loaded to the settings in the footer. + $loaded = array('CToolsAJAX' => array('scripts' => $js_files)); + // Save to the js settings array even though we do not reload it in advagg. + drupal_add_js($loaded, 'setting', 'footer'); + + // Add it to the settings array in the footer. + if (!isset($master_set['footer']['setting']) || !is_array($master_set['footer']['setting'])) { + $master_set['footer']['setting'] = array(); + } + $master_set['footer']['setting'][] = $loaded; +} + +/** + * Implement hook_advagg_css_pre_alter. + */ +function advagg_advagg_css_pre_alter(&$css, $preprocess_css, $public_downloads) { + // Don't run the code below if ctools ajax is not loaded. + if (!defined('CTOOLS_AJAX_INCLUDED')) { + return; + } + + // Get all CSS files set to be loaded. + $css_files = array(); + ctools_process_css_files($css_files, $css); + + // Save to the js settings array. + drupal_add_js(array('CToolsAJAX' => array('css' => $css_files)), 'setting', 'footer'); +} + +/** + * Create a list of javascript files that are on the page. + * + * @param $js_files + * Array of js files that are loaded on this page. + * @param $scope + * String usually containing header or footer. + * @param $scripts + * (Optional) array returned from drupal_add_js(). If NULL then it will load + * the array from drupal_add_js for the given scope. + * @return array $settings + * The JS 'setting' array for the given scope. + */ +function advagg_ctools_process_js_files(&$js_files, $scope, $scripts = NULL) { + // Automatically extract any 'settings' added via drupal_add_js() and make + // them the first command. + $scripts = drupal_add_js(NULL, NULL, $scope); + if (empty($scripts)) { + $scripts = drupal_add_js(NULL, NULL, $scope); + } + + // Get replacements that are going to be made by contrib modules and take + // them into account so we don't double-load scripts. + static $replacements = NULL; + if (!isset($replacements)) { + $replacements = module_invoke_all('js_replacements'); + } + + $settings = array(); + foreach ($scripts as $type => $data) { + switch ($type) { + case 'setting': + $settings = $data; + break; + case 'inline': + case 'theme': + // Presently we ignore inline javascript. + // Theme JS is already added and because of admin themes, this could add + // improper JS to the page. + break; + default: + // If JS preprocessing is off, we still need to output the scripts. + // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. + foreach ($data as $path => $info) { + // If the script is being replaced, take that replacement into account. + $final_path = isset($replacements[$type][$path]) ? $replacements[$type][$path] : $path; + $js_files[base_path() . $final_path] = TRUE; + } + } + } + return $settings; +} + +/** + * Wrapper around clearstatcache so it can use php 5.3's new features. + * + * @param $clear_realpath_cache + * Bool. + * @param $filename + * String. + * @return + * value from clearstatcache(). + */ +function advagg_clearstatcache($clear_realpath_cache = FALSE, $filename = NULL) { + static $php530; + if (!isset($php530)) { + $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); + } + + if ($php530) { + return clearstatcache($clear_realpath_cache, $filename); + } + else { + return clearstatcache(); + } +} + +/** + * Select records in the database matching where IN(...). + * + * NOTE Be aware of the servers max_packet_size variable. + * + * @param $table + * The name of the table. + * @param $field + * field name to be compared to + * @param $placeholder + * db_query placeholders; like %d or '%s' + * @param $data + * array of values you wish to compare to + * @param $returns + * array of db fields you return + * @return + * returns db_query() result. + */ +function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') { + // Set returns if empty + if (empty($returns)) { + $returns[] = '*'; + } + // Get the number of rows that will be inserted + $rows = count($data); + // Create what goes in the IN () + $in = $placeholder; + // Add the rest of the place holders + for ($i = 1; $i < $rows; $i++) { + $in .= ', ' . $placeholder; + } + // Build the query + $query = "SELECT " . implode(', ', $returns) . " FROM {" . $table . "} WHERE $field IN ($in) $groupby"; + // Run the query + return db_query($query, $data); +} + +/** + * Return a large array of the CSS & JS files loaded on this page. + * + * @param $js_files_excluded + * array of js files to not include in the output array. + * @param $css_files_excluded + * array of css files to not include in the output array. + * @return + * array. + */ +function advagg_get_js_css_get_array($js_files_excluded = array(), $css_files_excluded = array()) { + global $conf, $_advagg; + + // Setup variables. + $variables = array( + 'css' => array(), + 'js' => array(), + ); + // Setup render functions. + $css_function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION); + $js_function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION); + $conf['advagg_css_render_function'] = 'advagg_css_array'; + $conf['advagg_js_render_function'] = 'advagg_js_array'; + + // Run CSS code. + $css_array = array(); + $variables['css'] = drupal_add_css(); + if (module_exists('less')) { + less_preprocess_page($variables, NULL); + } + $css_func_inline = advagg_add_css_inline(); + if (!empty($css_func_inline)) { + $variables['css'] = advagg_merge_inline_css($variables['css'], $css_func_inline); + } + // Remove excluded CSS files. + foreach ($variables['css'] as $media => $types) { + foreach ($types as $type => $values) { + foreach ($values as $filename => $preprocess) { + if (in_array($filename, $css_files_excluded)) { + unset($variables['css'][$media][$type][$filename]); + } + } + } + } + $css_array = advagg_process_css($variables['css']); + + // Run JS code. + $js_array = array(); + $variables['js']['header'] = drupal_add_js(NULL, NULL, 'header'); + if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) { + $variables['js']['footer'] = drupal_add_js(NULL, NULL, 'footer'); + } + advagg_jquery_updater($variables['js']['header']); + // Remove excluded JS files. + foreach ($variables['js'] as $scope => $values) { + foreach ($values as $type => $data) { + foreach ($data as $filename => $info) { + if (in_array($filename, $js_files_excluded)) { + unset($variables['js'][$scope][$type][$filename]); + } + } + } + } + $js_array = advagg_process_js($variables['js']); + + // Set render functions back to defaults. + $conf['advagg_css_render_function'] = $css_function; + $conf['advagg_js_render_function'] = $js_function; + + // Return arrays. + return array( + 'js' => $js_array, + 'css' => $css_array, + ); +} + +/** + * Logic to figure out what kind of css tags to use. + * + * @param $external_no_preprocess + * array of css files ($media, $href) + * @param $module_no_preprocess + * array of css files ($media, $href) + * @param $output_no_preprocess + * array of css files ($media, $href) + * @param $output_preprocess + * array of css files ($media, $href, $prefix, $suffix) + * @param $theme_no_preprocess + * array of css files ($media, $href) + * @param $inline_no_preprocess + * array of css data to inline ($media, $data) + * @param $files_included + * array of css files included. $a[$media][] = $filename + * @param $files_aggregates_included + * array of css files & aggregates included. $a[$media][] = $filename + * @param $inline_included + * array of inline css included. $a[$media][] = $datablob; + * @return + * html for loading the css. html for the head. + */ +function advagg_css_array($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included) { + return array( + 'inline' => $inline_included, + 'files' => $files_included, + 'files_aggregates' => $files_aggregates_included, + ); +} + +/** + * Build and theme JS output for header. + * + * @param $external_no_preprocess + * array(array($src, $defer)) + * @param $output_preprocess + * array(array($src, $prefix, $suffix)) + * @param $output_no_preprocess + * array(array(array($src, $defer))) + * @param $setting_no_preprocess + * array(array($code)) + * @param $inline_no_preprocess + * array(array($code, $defer)) + * @param $scope + * header or footer. + * @param $js_settings_array + * array of settings used. + * @param $inline_included + * array of inline scripts used. + * @param $files_included + * array of files used. + * @param $files_aggregates_included + * array of files and aggregates used. + * @return + * String of themed JavaScript. + */ +function advagg_js_array($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) { + return array( + 'settings' => $js_settings_array, + 'inline' => $inline_included, + 'files' => $files_included, + 'files_aggregates' => $files_aggregates_included, + ); +} + +/** + * Implementation of hook_file_download(). + * + * Return the correct headers for advagg bundles. + */ +function advagg_file_download($file, $type = '') { + // Do nothing if not an AdvAgg File. + if (strpos($file, '/advagg_') === FALSE || empty($type)) { + return; + } + + // Set the headers. + $return = array(); + $return[] = 'Content-Length: ' . filesize($file); + // Set a far future Cache-Control header (480 weeks), which prevents + // intermediate caches from transforming the data and allows any + // intermediate cache to cache it, since it's marked as a public resource. + $return[] = "Cache-Control: max-age=290304000, no-transform, public"; + // Set a far future Expires header. The maximum UNIX timestamp is somewhere + // in 2038. Set it to a date in 2037, just to be safe. + $return[] = 'Expires: Tue, 20 Jan 2037 04:20:42 GMT'; + // Pretend the file was last modified a long time ago in the past, this will + // prevent browsers that don't support Cache-Control nor Expires headers to + // still request a new version too soon (these browsers calculate a + // heuristic to determine when to request a new version, based on the last + // time the resource has been modified). + // Also see http://code.google.com/speed/page-speed/docs/caching.html. + $return[] = 'Last-Modified: Wed, 20 Jan 1988 04:20:42 GMT'; + + if ($type == 'css') { + $return[] = 'Content-Type: text/css'; + } + if ($type == 'js') { + $return[] = 'Content-Type: text/javascript'; + } + return $return; +} + +/** + * Helper function to build an URL for asynchronous requests. + * + * @param $filepath + * Path to a URI excluding everything to the left and including the base path. + */ +function _advagg_build_url($filepath = '') { + global $base_path; + + // Server auth. + $auth = ''; + if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic') { + $auth = $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'] . '@'; + } + + // Host. + $ip = variable_get('advagg_server_addr', FALSE); + if ($ip == -1) { + $ip = $_SERVER['HTTP_HOST']; + } + elseif (empty($ip)) { + $ip = empty($_SERVER['SERVER_ADDR']) ? '127.0.0.1' : $_SERVER['SERVER_ADDR']; + } + + // Port. + $port = ''; +// if ( isset($_SERVER['SERVER_PORT']) +// && is_numeric($_SERVER['SERVER_PORT']) +// && ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443) +// ) { +// $port = ':' . $_SERVER['SERVER_PORT']; +// } + + return 'http://' . $auth . $ip . $port . $base_path . $filepath; +} diff --git a/sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc b/sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc new file mode 100644 index 0000000..97e94f3 --- /dev/null +++ b/sites/all/modules/advagg/advagg_bundler/advagg_bundler.admin.inc @@ -0,0 +1,96 @@ + 'checkbox', + '#title' => t('Bundler is Active'), + '#default_value' => variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE), + '#description' => t('If not checked, the bundler will passively monitor your site, but it will not split up aggregates.'), + ); + + $options = array( + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 7, + 8 => 8, + 9 => 9, + 10 => 10, + ); + $form['advagg_bundler_max_css'] = array( + '#type' => 'select', + '#title' => t('Max Number Of CSS Bundles Per Page'), + '#default_value' => variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS), + '#options' => $options, + '#description' => t('If 0 is selected then the bundler is disabled'), + ); + $form['advagg_bundler_max_js'] = array( + '#type' => 'select', + '#title' => t('Max Number Of JS Bundles Per Page'), + '#default_value' => variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS), + '#options' => $options, + '#description' => t('If 0 is selected then the bundler is disabled'), + ); + + $form['info'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Raw Grouping Info'), + ); + module_load_include('inc', 'advagg', 'advagg.admin'); + $analysis = advagg_bundler_analysis('', TRUE); + asort($analysis); + $analysis = array_reverse($analysis); + $data = array(); + foreach ($analysis as $filename => $key) { + $data[$key][] = $filename; + } + + list($rawtext, $rows) = advagg_form_print_r($data); + $form['info']['advagg_bundler_info'] = array( + '#type' => 'textarea', + '#title' => t('%count different groupings', array('%count' => count($data))), + '#default_value' => $rawtext, + '#rows' => $rows -1, + ); + + return system_settings_form($form); +} + +/** + * Validate form values. Used to unset variables before they get saved. + */ +function advagg_bundler_admin_settings_form_validate($form, &$form_state) { + global $conf; + + cache_clear_all('*', 'cache_advagg_bundle_reuse', TRUE); + + // Remove non variable form info. + unset($form_state['values']['advagg_bundler_info']); +} diff --git a/sites/all/modules/advagg/advagg_bundler/advagg_bundler.info b/sites/all/modules/advagg/advagg_bundler/advagg_bundler.info new file mode 100644 index 0000000..3942358 --- /dev/null +++ b/sites/all/modules/advagg/advagg_bundler/advagg_bundler.info @@ -0,0 +1,12 @@ +name = AdvAgg Bundler +description = Provides intelligent bundling of CSS and JS files by grouping files that belong together. +package = Advanced CSS/JS Aggregation +core = 6.x +dependencies[] = advagg + +; Information added by drupal.org packaging script on 2012-06-25 +version = "6.x-1.9" +core = "6.x" +project = "advagg" +datestamp = "1340665277" + diff --git a/sites/all/modules/advagg/advagg_bundler/advagg_bundler.install b/sites/all/modules/advagg/advagg_bundler/advagg_bundler.install new file mode 100644 index 0000000..ad8c182 --- /dev/null +++ b/sites/all/modules/advagg/advagg_bundler/advagg_bundler.install @@ -0,0 +1,52 @@ + 'Bundler', + 'description' => 'Adjust Bundler settings.', + 'page callback' => 'advagg_bundler_admin_page', + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_bundler.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implement hook_advagg_filenames_alter. + */ +function advagg_bundler_advagg_filenames_alter(&$filenames) { + // Get max number of sub aggregates to create. + $max_css = variable_get('advagg_bundler_max_css', ADVAGG_BUNDLER_MAX_CSS); + $max_js = variable_get('advagg_bundler_max_js', ADVAGG_BUNDLER_MAX_JS); + $schema = advagg_get_server_schema(); + + $output = array(); + foreach ($filenames as $values) { + // Extract values and set them. + $filetype = $values['filetype']; + $files = $values['files']; + $bundle_md5 = $values['bundle_md5']; + $cached_data_key = 'bundler_' . $schema . '_' . $bundle_md5; + + // Try cache first; cache table is cache_advagg_bundle_reuse. + $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_bundler_filenames_alter'); + if (!empty($cached_data)) { + $output = array_merge($output, $cached_data); + continue; + } + + // If cache miss then start to do processing. + // Set the max value based off of the filetype. + if ($filetype == 'css') { + $max = $max_css; + } + if ($filetype == 'js') { + $max = $max_js; + } + + // If we are only going to have one bundle then do not do any more + // processing. + if (empty($max) || $max == 1) { + $output[] = $values; + $data = array($values); + cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + continue; + } + + // Load up each file. + $groupings = array(); + // Preserve the order while grouping. + $last_group = ''; + foreach ($files as $key => $filename) { + // Assign each file to their group. + $group = advagg_bundler_analysis($filename); + + // Set $last_group if this is the first run of this foreach loop. + if (empty($last_group)) { + $last_group = $group; + } + + if ($last_group == $group) { + // Include this new file into the last used group. + $groupings[$group][] = $filename; + } + else { + // In order to preserve CSS/JS execution order we need to move the last + // group to a unique name. Use the array key to make this group unique. + $groupings[$key . ' ' . $last_group] = $groupings[$last_group]; + unset($groupings[$last_group]); + // Place the new file into the new group and set $last_group. + $groupings[$group][] = $filename; + $last_group = $group; + } + } + + // If only one group then don't do any more processing. + if (count($groupings) == 1) { + $output[] = $values; + $data = array($values); + cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + continue; + } + + // Make sure we didn't go over the max; if we did merge the smallest bundles + // together. + advagg_bundler_merge($groupings, $max); + + // If only one group then don't do any more processing. The merge algorithm + // could have reduce the groupings down to one. + if (count($groupings) == 1) { + $output[] = $values; + $data = array($values); + cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + continue; + } + + // Set groups. + $data = array(); + foreach ($groupings as $bundle) { + $values['files'] = $bundle; + $values['bundle_md5'] = md5($schema . implode('', $bundle)); + $data[] = $values; + $output[] = $values; + } + cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); + } + + // Return groupings. + if (variable_get('advagg_bundler_active', ADVAGG_BUNDLER_ACTIVE)) { + $filenames = $output; + } +} + +/** + * Given a filename return a bundle key. + * + * @param $filename + * filename + * @param $force + * bypass the cache and get a fresh version of the analysis. + * @return + * string to be used for the grouping key. + */ +function advagg_bundler_analysis($filename = '', $force = FALSE) { + // Cache query in a static. + static $analysis = array(); + if (empty($analysis)) { + // See if we have a cached version of this. + $count = db_result(db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE root=1")); + $cache = cache_get('advagg_bundler_analysis:' . $count); + if (empty($cache->data) || $force) { + // "Magic Query"; only needs to run once. + // Return a count of how many root bundles all files are used in. Count is + // padded with eight zeros so the count can be key sorted as a string + // without worrying about it getting put in the wrong order. + // Return the bundle_md5's value; we need something more unique than count + // when grouping together. + // Return the filename. Used for lookup. + // We join the advagg bundles and files together making sure to only use + // root bundles that have been used in the last 2 weeks. This prevents an + // old site structure from influencing new bundles. + // Grouping by the filename gives us the count and makes it so we don't + // return a lot of rows; + $result = db_query(" + SELECT + count, + bundle_md5, + filename + FROM ( + SELECT + LPAD(CAST(COUNT(*) AS char(8)), 8, '0') AS count, + bundle_md5, + filename_md5 + FROM + {advagg_bundles} + WHERE + root = 1 + AND + timestamp > %d + GROUP BY + filename_md5) AS ab + INNER JOIN {advagg_files} AS af USING ( filename_md5 ) + ", time() - variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED)); + + // Save query into a static array with the filename as the key. + while ($row = db_fetch_array($result)) { + $analysis[$row['filename']] = $row['count'] . ' ' . $row['bundle_md5']; + } + + // Invoke hook_advagg_bundler_analysis_alter() to give installed modules a + // chance to alter the analysis array. + drupal_alter('advagg_bundler_analysis', $analysis); + + // Save results to the cache. + cache_set('advagg_bundler_analysis:' . $count, $analysis, 'cache', CACHE_TEMPORARY); + } + else { + $analysis = $cache->data; + } + } + + // If no filename is given pass back then entire query results. + if (empty($filename)) { + return $analysis; + } + + // Return a key to be used in groupings. + if (!empty($analysis[$filename])) { + return $analysis[$filename]; + } + + // We need to return a value that can be used as an array key if the query + // didn't give us anything. + return 0; +} + +/** + * Merge bundles together if too many where created. + * + * This preserves the order. + * + * @param $groupings + * array of requested groups + * @param $max + * max number of grouping + */ +function advagg_bundler_merge(&$groupings, $max) { + $group_count = count($groupings); + + if (!empty($max)) { + // Keep going till array has been merged to the desired size. + while ($group_count > $max) { + // Get array sizes. + // Counts the number of files that are placed into each bundle. + $counts = array(); + foreach ($groupings as $key => $values) { + $counts[$key] = count($values); + } + + // Create mapping. + // Calculates the file count of potential merges. It only merges with + // neighbors in order to preserve execution order. + $map = array(); + $prev_key = ''; + foreach ($counts as $key => $val) { + // First run of the foreach loop; populate prev key/values and continue. + // We can't merge with the previous group in this case. + if (empty($prev_key)) { + $prev_key = $key; + $prev_val = $val; + continue; + } + + // Array key ($prev_val + $val) is the file count of this new group if + // these 2 groups ($prev_key, $key) where to be merged together. + $map[] = array( + ($prev_val + $val) => array($prev_key, $key), + ); + $prev_key = $key; + $prev_val = $val; + } + + // Get best merge candidate. + // We are looking for the smallest file count. $min is populated with a + // large number (15 bits) so it won't be selected in this process. + $min = 32767; + foreach ($map as $v) { + foreach ($v as $key => $values) { + $min = min($min, $key); + // If the min value (number of files in the proposed merged bundle) is + // the same as the key, then get the 2 bundle keys that generated this + // new min value. + if ($min == $key) { + list($first, $last) = $values; + } + } + } + +// watchdog('debug', $first . "
\n" . $last . "
\n" . str_replace(' ', '    ', nl2br(htmlentities(print_r(array($groupings, $map, $counts), TRUE))))); + + // Create the new merged set + $a = $groupings[$first]; + $b = $groupings[$last]; + $new_set = array_merge($a, $b); + + // Rebuild the array with the new set in the correct place. + $new_groupings = array(); + foreach ($groupings as $key => $files) { + if ($key == $first) { + $new_groupings[$first . ' ' . $last] = $new_set; + } + elseif ($key != $last) { + $new_groupings[$key] = $files; + } + } + $groupings = $new_groupings; + $group_count--; + } + } + + // Make sure there isn't a group that has all files that don't exist or empty. + $bad_groups = array(); + $counts = array(); + foreach ($groupings as $key => $group) { + $missing_counter = 0; + $counts[$key] = count($group); + foreach ($group as $i => $file) { + advagg_clearstatcache(TRUE, $file); + if (!file_exists($file) || filesize($file) == 0) { + $missing_counter++; + } + } + + // If all files are missing/empty in this group then it is a bad set. + if ($missing_counter == count($group)) { + $bad_groups[$key] = TRUE; + } + } + + // Add the bad groups to the smallest grouping in this set. + if (!empty($bad_groups)) { + $merge_candidate_key = ''; + $merge_candidate_count = 32767; + $bad_group = array(); + foreach ($groupings as $key => $group) { + if (isset($bad_groups[$key])) { + // Merge all bad groups into one. + $bad_group = array_merge($bad_group, $group); + + // Delete the bad group from the master set. + unset($groupings[$key]); + continue; + } + + // Find the smallest good grouping. + $min = min($merge_candidate_count, count($group)); + if ($min < $merge_candidate_count) { + $merge_candidate_key = $key; + $merge_candidate_count = $min; + } + } + + // Move the bad files into the smallest good group. + $new_set = $groupings[$merge_candidate_key]; + $new_set = array_merge($new_set, $bad_group); + $groupings[$merge_candidate_key] = $new_set; + } + + // Output Debugging info to watchdog. + // watchdog('debug', str_replace(' ', '    ', nl2br(htmlentities(print_r($groupings, TRUE))))); +} diff --git a/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc b/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc new file mode 100644 index 0000000..61a2056 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.admin.inc @@ -0,0 +1,77 @@ + 'checkbox', + '#title' => t('Compress CSS Files'), + '#default_value' => variable_get('advagg_css_compress_agg_files', ADVAGG_CSS_COMPRESS_AGG_FILES), + ); + $form['advagg_css_compress_inline'] = array( + '#type' => 'checkbox', + '#title' => t('Compress Inline CSS'), + '#default_value' => variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE), + ); + $advagg_css_compressor = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR); + $form['advagg_css_compressor'] = array( + '#type' => 'radios', + '#title' => t('Select the compression library to use'), + '#default_value' => $advagg_css_compressor, + '#options' => array( + 0 => 'CSSTidy', + 1 => 'CSS Compressor', + 2 => 'YUI CSSMin', + ), + '#description' => t('CSSTidy is a well known CSS compression library, but it has some known issues/limitations.
CSS Compressor is a faster CSS compression alternative.
YUI CSSMin is a php port of the java library from yahoo and is fairly quick.
', + array( + '@csstidy' => 'https://github.com/Cerdic/CSSTidy', + '@csscompressor' => 'http://www.codenothing.com/css-compressor/', + '@cssmin' => 'http://code.google.com/p/minify/', + ) + ), + ); + if ($advagg_css_compressor == 0) { + $form['advagg_css_compress_preserve_css'] = array( + '#type' => 'checkbox', + '#title' => t('CSSTidy: Preserve CSS'), + '#default_value' => variable_get('advagg_css_compress_preserve_css', ADVAGG_CSS_COMPRESS_PRESERVE_CSS), + '#description' => t('If disabled CSS selectors will try to be merged together, significantly reducing the css file size. May result in broken layouts is disabled. This only applies to compression through the CSSTidy library.'), + ); + } + elseif ($advagg_css_compressor == 1) { + $form['advagg_css_compress_compressor_level'] = array( + '#type' => 'radios', + '#title' => t('CSS Compressor: Select the CSS Compression to use'), + '#default_value' => variable_get('advagg_css_compress_compressor_level', ADVAGG_CSS_COMPRESS_COMPRESSOR_LEVEL), + '#options' => array( + 'safe' => t('Safe mode (99% safe) does zero combinations or organizing. Its the best mode if you use a lot of CSS hacks.'), + 'sane' => t('Sane mode (90% safe) does most combinations (multiple long hand notations to single shorthand), but still keeps most declarations in their place.'), + 'small' => t('Small mode (65% safe) reorganizes the whole style sheet, combines as much as it can, and will break most comment hacks.'), + 'full' => t('Full mode (64% safe) does everything small does, but also converts hex codes to their short color name alternatives.'), + ), + '#description' => t('This only applies to compression through the CSS Compressor library.'), + ); + } + + return system_settings_form($form); +} diff --git a/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info b/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info new file mode 100644 index 0000000..30331ce --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.info @@ -0,0 +1,13 @@ +name = AdvAgg Compress CSS +description = Compress CSS with a 3rd party compressor, CSSTidy currently. +package = Advanced CSS/JS Aggregation +core = 6.x +dependencies[] = advagg +php = 5.0 + +; Information added by drupal.org packaging script on 2012-06-25 +version = "6.x-1.9" +core = "6.x" +project = "advagg" +datestamp = "1340665277" + diff --git a/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install b/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install new file mode 100644 index 0000000..b656a79 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/advagg_css_compress.install @@ -0,0 +1,91 @@ + 'CSS Compression', + 'description' => 'Adjust CSS Compression settings.', + 'page callback' => 'advagg_css_compress_admin_page', + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_css_compress.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implement hook_advagg_css_alter. + */ +function advagg_css_compress_advagg_css_alter(&$contents, $files, $bundle_md5) { + if (!variable_get('advagg_css_compress_agg_files', ADVAGG_CSS_COMPRESS_AGG_FILES)) { + return; + } + + $compressor = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR); + if ($compressor == 0) { + advagg_css_compress_css_tidy($contents); + } + elseif ($compressor == 1) { + advagg_css_compress_css_compressor($contents); + } + elseif ($compressor == 2) { + advagg_css_compress_yui_cssmin($contents); + } +} + +/** + * Implement hook_advagg_css_inline_alter. + */ +function advagg_css_compress_advagg_css_inline_alter(&$contents) { + if (!variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE)) { + return; + } + $compressor = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR); + + // If using a cache, try to get the contents of it. + if (variable_get('advagg_css_compress_inline_cache', ADVAGG_CSS_COMPRESS_INLINE_CACHE)) { + $key = md5($contents) . $compressor; + $table = 'cache_advagg_css_compress_inline'; + $data = cache_get($key, $table); + if (!empty($data->data)) { + $contents = $data->data; + return; + } + } + + if ($compressor == 0) { + advagg_css_compress_css_tidy($contents); + } + if ($compressor == 1) { + advagg_css_compress_css_compressor($contents); + } + + // If using a cache set it. + if (isset($key)) { + cache_set($key, $contents, $table, CACHE_TEMPORARY); + } +} + +/** + * Use the CSS Tidy library to compress the CSS. + * + * TODO have set_cfg be fully configurable from GUI. + */ +function advagg_css_compress_css_tidy(&$contents) { + // Initialize CSSTidy. + $filename = drupal_get_path('module', 'advagg_css_compress') . '/csstidy/class.csstidy.inc'; + include_once($filename); + $css = new csstidy(); + + // Try to allocate enough time to run CSSTidy. + if (function_exists('set_time_limit')) { + @set_time_limit(240); + } + + // Set configuration. + $css->set_cfg('preserve_css', variable_get('advagg_css_compress_preserve_css', ADVAGG_CSS_COMPRESS_PRESERVE_CSS)); + $css->set_cfg('remove_last_;', TRUE); + $css->set_cfg('merge_selectors', TRUE); + $css->set_cfg('optimise_shorthands', TRUE); + $css->set_cfg('silent', TRUE); + $css->set_cfg('compress_colors', TRUE); + $css->set_cfg('sort_selectors', FALSE); + $css->set_cfg('sort_properties', FALSE); + $css->set_cfg('discard_invalid_properties', FALSE); + $css->set_cfg('timestamp', FALSE); + $css->load_template("highest_compression"); + + // Compress CSS. + $css->parse($contents); + $contents = $css->print->plain(); +} + +/** + * Use the CSS Compressor library to compress the CSS. + * + * TODO have compression level be selectable from GUI. + */ +function advagg_css_compress_css_compressor(&$contents) { + // Initialize CSSTidy. + $filename = drupal_get_path('module', 'advagg_css_compress') . '/css-compressor-3.x/src/CSSCompression.inc'; + include_once($filename); + + $CSSC = new CSSCompression(variable_get('advagg_css_compress_compressor_level', ADVAGG_CSS_COMPRESS_COMPRESSOR_LEVEL)); + $contents = $CSSC->compress($contents); +} + +/** + * Use the CSSmin library from YUI to compress the CSS. + */ +function advagg_css_compress_yui_cssmin(&$contents) { + // Include CSS_Compressor from Stephen Clay. +// $filename = drupal_get_path('module', 'advagg_css_compress') . '/yui/Compressor.inc'; +// include_once($filename); +// $contents = preg_replace('/@charset[^;]+;\\s*/', '', $contents); +// $contents = Minify_CSS_Compressor::process($contents); + + // Include CSSmin from YUI. + $filename = drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc'; + include_once($filename); + $cssmin = new CSSmin(); + // Create a new line after 4k of text. + $contents = trim($cssmin->run($contents, 4096)); +} + + +/** + * Implementation of hook_flush_caches(). + */ +function advagg_css_compress_flush_caches() { + return array('cache_advagg_css_compress_inline'); +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/Makefile b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/Makefile new file mode 100644 index 0000000..6bbf736 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/Makefile @@ -0,0 +1,34 @@ +# +# CSS Compressor [VERSION] +# [DATE] +# Corey Hart @ http://www.codenothing.com +# +.PHONY: benchmark test +VERSION=temp + +all: + @echo "\n\x1B[1;31mPC_LOAD_LETTER\x1B[0m\n" + +test: + @php unit/start.inc + +test-all: + @php unit/start.inc all + +test-focus: + @php unit/focus.inc + +test-file: + @php unit/file.inc + +test-regression: + @php unit/benchmark/benchmark.inc $(VERSION) + +test-cli: + @php cli/cli.inc -i cli/test/a.css > cli/test/result.css + +benchmark: + @php unit/benchmark/benchmark.inc + +clean: + @sh unit/clean.sh diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/README.md b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/README.md new file mode 100644 index 0000000..aed1632 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/README.md @@ -0,0 +1,137 @@ +[CSS Compressor](http://www.codenothing.com/css-compressor/) +============================================================ + +CSSCompression is a PHP based CSS minifier that analyzes stylesheets for various compressions. +It finds possible CSS shorthand techniques for combination of properties. + + +Usage +----- + + require( 'src/CSSCompression.inc' ); + $compressed = CSSCompression::express( $css, 'sane' ); + + +Or, if you need to run it multiple times + + $CSSC = new CSSCompression( 'sane' ); + $compressed = $CSSC->compress( $css ); + + +Modes +----- + +Modes are pre-defined sets of options that can be set by passing in the mode name. + + - **safe**: (99% safe) Safe mode does zero combinations or organizing. It's the best mode if you use a lot of hacks. + + - **sane**: (90% safe) Sane mode does most combinations(multiple long hand notations to single shorthand), but still keeps most declarations in their place. + + - **small**: (65% safe) Small mode reorganizes the whole sheet, combines as much as it can, and will break most comment hacks. + + - **full**: (64% safe) Full mode does everything small does, but also converts hex codes to their short color name alternatives. + + +Here's a few different ways to initiate a mode. + + // Express with safe mode + $compressed = CSSCompression::express( $css, 'safe' ); + + // Creating new instance with sane mode + $CSSC = new CSSCompression( 'sane' ); + + // Setting an instance with small mode + $CSSC->mode( 'small' ); + + // Or compressing with the current instance, and setting full mode + $compressed = $CSSC->compress( $css, 'full' ); + + + +Singleton Instances +------------------- + +Yes the compressor provides singleton access(separate from express), but use it wisely. + + $CSSC = CSSCompression::getInstance(); + + // Or, if you want to keep named instances + $rose_instance = CSSCompression::getInstance('rose'); + $blue_instance = CSSCompression::getInstance('blue'); + + +Option Handling +--------------- + +The compressor has an option function with multiple functionalities. + + - If no arguments are passed in, the entire options array is returned. + + - If a single name argument is passed, then the value of that key name in the options array is returned. + + - If both a name and value are passed, then that value is set to it's corresponding key in the array. + +Usage + + // Returns entire options array + $options = $CSSC->option(); + + // Returns the readability option value + $readability = $CSSC->option( 'readability' ); + + // Sets the readability to non-readable + $CSSC->option( 'readability', CSSCompression::READ_NONE ); + +Alternatively, you can use direct access methods + + // Direct access to options array + $options = $CSSC->options; + + // Direct access to readability option value + $readability = $CSSC->readability; + + // Direct setting of readability value + $CSSC->readability = CSSCompression::READ_NONE; + +Additionally, a reset function is provided to revert back to base options (decided at runtime). + + // Resets options to original values + $CSSC->reset(); + + + +Readability +----------- + +The compressor class provides static integers that map to the internal readability values + + CSSCompression::READ_MAX // Maximum Readability + CSSCompression::READ_MED // Medium readability of output + CSSCompression::READ_MIN // Minimal readability of output + CSSCompression::READ_NONE // No readability of output (full compression into single line) + + // To set maximum readability (Assuming you have your own instance) + $CSSC->option( 'readability', CSSCompression::READ_MAX ); + + // Or, just pass it in as another option + $options = array( + 'readability' => CSSCompression::READ_MAX, + // Other options ... + ); + // Get full readability through express + $compressed = CSSCompression::express( $css, $options ); + + +Contributors +------------ +[Corey Hart](http://www.codenothing.com) - Creator + +[Martin Zvarík](http://www.teplaky.net/) - Pointed out the url and empty definition bug. + +[Phil DeJarnett](http://www.overzealous.com/) - Pointed out splitting(and numerous other) problems + +[Stoyan Stefanov](http://www.phpied.com/) - [At rules writeup](http://www.phpied.com/css-railroad-diagrams/) and test suite help. + +[Julien Deniau](http://www.jeuxvideo.fr/) - Pointed out escaped characters issue + +[Olivier Gorzalka](http://clearideaz.com/) - Strict error warnings cleanup diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/changelog b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/changelog new file mode 100644 index 0000000..6d4d84b --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/changelog @@ -0,0 +1,22 @@ +# +# CSS Compression [VERSION] Change Log +# [DATE] +# Corey Hart @ http://www.codenothing.com +# + +# Compressor Additions +- Configuration Modes +- pseduo-space, add space between pseduo selectors and comma/brace for ie6 +- removing leading 0 of a decimal, 0.633px -> .633px +- handling all At Rules (charsets,media,imports,font-family,etc..) +- handling ms filter paths +- More css hacks support, including box-modal hack +- attr2selector, Convert id=blah and class=blah attributes to their selector counterparts +- strict-id, removes all selectors up to the last id +- add-unknown, adds unknown blocks to top of output in a comment + +# Codebase Changes +- Converted to subclass architecture, more modular. +- Using JSON based sandbox specs for focused unit tests. +- Helper conversion definitions(long2hex, hex2short) are now in JSON based files. +- Added benchmark/regression testing diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/CSSCompression.md b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/CSSCompression.md new file mode 100644 index 0000000..4d1c708 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/CSSCompression.md @@ -0,0 +1,263 @@ +Class CSSCompression +==================== + +Below is a description of all public access points to the compressor. + + +const *bool* VERSION +-------------------- + +Release version + + +const *bool* DATE +----------------- + +Release Date + + +const *bool* DEV +---------------- + +Signifies development mode. When true, back door access to all subclasses is enabled. Should always be false in production. + + +const *bool* TOKEN +------------------ + +Special marker that gets injected and removed into the stylesheet during compression. Change this if it exists in your sheet. + + +public static *array* defaults +------------------------------ + +Default settings for every instance. + + +const *int* READ_MAX, const *int* READ_MED, const *int* READ_MIN, const *int* READ_NONE +--------------------------------------------------------------------------------------- + +Readability constants. Tells what format to return the css back after compression. + + +Getters +======= + +This is the list of readable vars on any given instance + + - *string* **css**: Contains the compressed result of the last compression ran. + - *string* **mode**: Contains the current mode of the instance + - *array* **options**: Contains the complete array of currently defined options + - *array* **stats**: Contains the result stats of the last compression ran. + - *string* **(option-name)**: Contains the value of that option **name**. + +Usage: + + // Print out compressed css + echo $CSSC->css; + + // Print out the current mode + echo $CSSC->mode; + + // Print out list of options + print_r( $CSSC->options ); + + // Print out result stats + print_r( $CSSC->stats ); + + // Print out a single options value + echo $CSSC->readability; + + +Setters +======= + +Currently, you can only directly set options + + - *string* **options**, *array* **value**: Merge an array of options with the current defaults + - *string* **name**, *mixed* **value**: Set the option **name** with the **value**. + +Usage: + + // Merge a custom set of options into the defined set + // Remember that it doesn't set, just merges + $CSSC->options = array( 'readability' => CSSCompression::READ_MAX, 'organize' => true ); + + // Set a single options value + $CSSC->readability = CSSCompression::READ_MAX; + $CSSC->organize = true; + + + +public function __construct( [ *mixed* $css = NULL, *mixed* $options = NULL ] ) +------------------------------------------------------------------------------- + +Builds the subclasses first, then does one of the following + + - Passing a single string argument that is the name of a mode, sets the mode for this instance. + + - Passing a single array of options argument will merge those with the defaults for this instance. + + - Passing a single long string argument, that is not the name of a mode, will run compression with the defaults set. + + - Passing a long string argument, and a mode name argument, sets the mode's options, and then runs compression on the css string. + + - Passing a long string argument, and an array of options argument, merges those with default options, and runs compression on the css string. + +Usage: + + // Create an instance in 'sane' mode + $CSSC = new CSSCompression( 'sane' ); + + // Create an instance with a custom set of options + $CSSC = new CSSCompression( array( 'readability' => CSSCompression::READ_MAX ) ); + + // Creates a new instance, then runs compression on the css passed + $CSSC = new CSSCompression( $css ); + echo $CSSC->css; + + // Creates a new instance in 'sane' mode, then runs compression on the css passed + $CSSC = new CSSCompression( $css, 'sane' ); + echo $CSSC->css; + + // Creates a new instance with a custom set of options, then runs compression on the css passed + $CSSC = new CSSCompression( $css, array( 'readability' => CSSCompression::READ_MAX ) ); + echo $CSSC->css; + + +*array* public function mode( *string* $mode = NULL ) +----------------------------------------------------- + +Sets the mode of the instance. + + // Set this instance to 'sane' mode + $CSSC->mode( 'sane' ); + + +*array* public static function modes( [ *mixed* $mode = NULL, *array* $config = NULL ] ) +---------------------------------------------------------------------------------------- + +Mode configuration, any one of the following combination of arguments is allowed + + - Passing no arguments returns the entire array of modes. + + - Passing only a string mode argument returns that modes configuration. + + - Passing a string mode argument, and an array config argument sets that config to the mode. + + - Passing a single array argument merges a set of modes into the configured set + +Usage: + + // Returns the entire list of modes + $modes = CSSCompression::modes(); + + // Returns 'sane' mode configuration + $sane = CSSCompression::modes( 'sane' ); + + // Add 'rose' mode to the list of modes + CSSCompression::modes( 'rose', array( 'organize' => false, 'readability' => CSSCompression::READ_MAX ) ); + + // Add 'rose' and 'blue' mode configurations to set of modes + CSSCompression::modes(array( + 'rose' => array( 'organize' => false, 'readability' => CSSCompression::READ_MAX ), + 'blue' => array( 'rm-multi-define' => false, 'readability' => CSSCompression::READ_NONE ) + )); + +**NOTE:** When an instance configures itself to a mode, it sets every option to true, and expects the mode configuration to tell it what is false. + + +*mixed* public function option( [ *mixed* $name = NULL, *mixed* $value = NULL ] ) +--------------------------------------------------------------------------------- + +Custom option handling, any one of the following may happen + + - Passing no arguments returns the entire array of options currently set. + + - Passing only a string name argument returns the value for that option. + + - Passing a single array argument merges those into the current options of the instance. + + - Passing a string name argument, and a value argument sets the value to it's corresponding option name. + +Usage: + + // Get the entire options array for this instance + $options = $CSSC->option(); + + // Get the current readability value for this instance + $readability = $CSSC->option( 'readability' ); + + // Merge a set of options into the current instance + $CSSC->option( array( 'organize' => false, 'readability' => CSSCompression::READ_MAX ) ); + + // Set the readability of the current object to full + $CSSC->option( 'readability', CSSCompression::READ_MAX ); + + +*string* public function compress( *string* $css = NULL, [ *mixed* $options = NULL ] ) +-------------------------------------------------------------------------------------- + +Compresses the given string with the given options/mode. $options can be the name of a mode, or an array of options. + + // Compress the css passed + $compressed = $CSSC->comrpess( $css ); + + // Compress the css in 'sane' mode + $compressed = $CSSC->comrpess( $css, 'sane' ); + + // Compress the css with a custom set of options + $compressed = $CSSC->comrpess( $css, array( 'readability' => CSSCompression::READ_MAX ) ); + + +*string* public static function express( *string* $css = NULL, [ *mixed* $options = NULL ] ) +-------------------------------------------------------------------------------------------- + +Use's it's own singleton instance to return compressed css sheets. $options can be the name of a mode, or an array of options. + + // Compress the css passed + $compressed = CSSCompression::express( $css ); + + // Compress the css in 'sane' mode + $compressed = CSSCompression::express( $css, 'sane' ); + + // Compress the css with a custom set of options + $compressed = CSSCompression::express( $css, array( 'readability' => CSSCompression::READ_MAX ) ); + + +*bool* public function reset() +------------------------------ + +Cleans out compression instance, all of it's subclasses, and resets options back to their defaults. + + // Reset this instance to it's defaults + $CSSC->reset(); + + +*bool* public function flush() +------------------------------ + +Cleans out class vars. + + // Flush out compression variables + $CSSC->flush(); + + +*object* public static function getInstance( [ *string* name = NULL ] ) +----------------------------------------------------------------------- + +Returns a singleton instance of the compressor + + // Get a singleton instance + $CSSC = CSSCompression::getInstance(); + + // Get the stored 'rose' singleton instance + $CSSC = CSSCompression::getInstance( 'rose' ); + + +*array* public static function getJSON( *string* $file ) +-------------------------------------------------------- + +Pulls the contents of the $file, does some quick comment stripping, then returns a json decoded hash. Mainly for internal use. + + $json = CSSCompression::getJSON( "/path/to/my/file.json" ); diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Contribute.md b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Contribute.md new file mode 100644 index 0000000..a06a3ca --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Contribute.md @@ -0,0 +1,63 @@ +Contribute +========== + +This project is always in dire need of help in any form, I only have 2 rules: + +- All sandboxed tests must pass. + +For nix users: From the root directory, run 'make test' +For windows users: Run 'php unit/start.inc' + +- Tab character indentations + +I don't care if you like 2,3,4,8,16 spaced indentation, just make sure it's a tab character and not spaces. + + +make test +--------- + +This command will run all sandboxed tests to check that most functions run the way they should. + + +make test-focus +--------------- + +This command runs a focused test on a single function for development. Open up unit/focus.inc for configuration. + + +make test-file +-------------- + +This command runs a focused test on a single file. Make sure the original resides in unit/sheets/original/ and the expected +output resides in unit/sheets/expected/. Open up unit/file.inc for configuration + + +make test-all +------------- + +This command runs all sandboxed tests, as well as double compressions on any stylesheets in benchmark src. Doesn't always pass, +but is a helpful check to see that most compression is done the first time around. + + +make test-regression [ VERSION=temp ] +-------------------------------- + +The regression command takes an optional assignment which will do testing against the given version. Defaults is to the last run test. + + +make benchmark +-------------- + +This command runs the benchmark process. This will create a new temp directory for regression testing and comparison. + + +make clean +---------- + +This command removes all generated files used for comparisons and benchmarks. + + +unit/sandbox/ +------------- + +The sandbox directory contains json files for focused tests used in unit testing. diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Options.md b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Options.md new file mode 100644 index 0000000..c9abe81 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/docs/Options.md @@ -0,0 +1,234 @@ +Options +======= + +Here's a few different ways to set options. + + // Set an array of options + $options = array( 'color-long2hex' => false, 'readability' => CSSCompression::READ_MAX ); + + // Pass directly into express compression + $compressed = CSSCompression::express( $css, $options ); + + // Create an instance based on the predefined set of options + $CSSC = new CSSCompression( $options ); + + // Set a batch of options on an instance + $CSSC->option( $options ); + + // Set a single option on an instance + $CSSC->option( 'readability', CSSCompression::READ_MAX ); + + // Or, if you just want to read an option + $readability = $CSSC->option( 'readability' ); + + // Also, you can look at the current options + $options = $CSSC->option(); + + +color-long2hex +-------------- + +Converts long color names to short hex names + + - *aliceblue -> #f0f8ff* + + +color-rgb2hex +------------- + +Converts rgb colors to hex + + - *rgb(159,80,98) -> #9F5062, rgb(100%) -> #FFFFFF* + + +color-hex2shortcolor +-------------------- + +Converts long hex codes to short color names, Only works on latest browsers, careful when using. + + - *#f5f5dc -> beige* + + +color-hex2shorthex +------------------ + +Converts long hex codes to short hex codes + + - *#44ff11 -> #4f1* + + +color-hex2safe +-------------------- + +Converts long hex codes to safe CSS Level 1 color names. + + - *#f00 -> red* + + +fontweight2num +-------------- + +Converts font-weight names to numbers + + - *bold -> 700* + + +format-units +------------ + +Removes zero decimals and 0 units + + - *15.0px -> 15px || 0px -> 0* + + +lowercase-selectors +------------------- + +Lowercases html tags from list + + - *BODY -> body* + + +attr2selector +------------- + +Converts class and id attributes to their shorthand counterparts + + - *div[id=blah][class=blah] -> div#blah.blah* + + +strict-id +--------- + +Promotes nested id's to the front of the selector + + - *body > div#elem p -> #elem p* + + +pseudo-space +------------ + +Add space after :first-letter and :first-line pseudo selectors, for ie6 + + - *a:first-line{ -> a:first-line {* + + +directional-compress +-------------------- + +Compresses single defined multi-directional properties + + - *margin: 15px 25px 15px 25px -> margin:15px 25px* + + +organize +-------- + +Combines multiply defined selectors and details + + - *p{color:blue;} p{font-size:12pt} -> p{color:blue;font-size:12pt;}* + - *p{color:blue;} a{color:blue;} -> p,a{color:blue;}* + + + +csw-combine +----------- + +Combines color/style/width properties + + - *border-style:dashed;border-color:black;border-width:4px; -> border:4px dashed black* + + +auralcp-combine +--------------- + +Combines cue/pause properties + + - *cue-before: url(before.au); cue-after: url(after.au) -> cue:url(before.au) url(after.au)* + + +mp-combine +---------- + +Combines margin/padding directionals + + - *margin-top:10px;margin-right:5px;margin-bottom:4px;margin-left:1px; -> margin:10px 5px 4px 1px;* + + +border-combine +-------------- + +Combines border directionals + + - *border-top|right|bottom|left:1px solid black -> border:1px solid black* + + +font-combine +------------ + +Combines font properties + + - *font-size:12pt; font-family: arial; -> font:12pt arial* + + +background-combine +------------------ + +Combines background properties + + - *background-color: black; background-image: url(bgimg.jpeg); -> background:black url(bgimg.jpeg)* + + +list-combine +------------ + +Combines list-style properties + + - *list-style-type: round; list-style-position: outside -> list-style:round outside* + + +border-radius-combine +--------------------- + +Combines border-radius properties + + { + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + } + -> { border-radius: 10px; } + + +unnecessary-semicolons +---------------------- + +Removes the last semicolon of a rule set + + - *{margin: 2px; color: blue;} -> {margin: 2px; color: blue}* + + +rm-multi-define +--------------- + +Removes multiple declarations within the same rule set + + - *{color:black;font-size:12pt;color:red;} -> {color:red;font-size:12pt;}* + + +add-unknown +----------- + +Adds unknown artifacts to a comment block at the top of output. + + +readability +----------- + +Readability of Compressed Output. + + CSSCompression::READ_MAX; // Maximum readability + CSSCompression::READ_MED; // Medium readability + CSSCompression::READ_MIN; // Minimum readability + CSSCompression::READ_NONE; // No readability diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/license.txt b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/license.txt new file mode 100644 index 0000000..1dd0c70 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/license.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 Corey Hart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/CSSCompression.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/CSSCompression.inc new file mode 100644 index 0000000..2165fd0 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/CSSCompression.inc @@ -0,0 +1,438 @@ + #f0f8ff) + 'color-long2hex' => true, + // Converts rgb colors to hex + // (rgb(159,80,98) -> #9F5062, rgb(100%) -> #FFFFFF) + 'color-rgb2hex' => true, + // Converts long hex codes to short color names (#f5f5dc -> beige) + // Only works on latest browsers, careful when using + 'color-hex2shortcolor' => false, + // Converts long hex codes to short hex codes + // (#44ff11 -> #4f1) + 'color-hex2shorthex' => true, + // Converts hex codes to safe CSS Level 1 color names + // (#F00 -> red) + 'color-hex2safe' => true, + // Converts font-weight names to numbers + // (bold -> 700) + 'fontweight2num' => true, + // Removes zero decimals and 0 units + // (15.0px -> 15px || 0px -> 0) + 'format-units' => true, + // Lowercases html tags from list + // (BODY -> body) + 'lowercase-selectors' => true, + // Converts id and class attribute selectors, to their short selector counterpart + // (div[id=blah][class=moreblah] -> div#blah.moreblah) + 'attr2selector' => true, + // Promotes nested id's to the front of the selector + // (body>div#elem p -> $elem p) + 'strict-id' => false, + // Add space after pseudo selectors, for ie6 + // (a:first-child{ -> a:first-child {) + 'pseudo-space' => false, + // Compresses single defined multi-directional properties + // (margin: 15px 25px 15px 25px -> margin:15px 25px) + 'directional-compress' => true, + // Combines multiply defined selectors and details + // (p{color:blue;} p{font-size:12pt} -> p{color:blue;font-size:12pt;}) + // (p{color:blue;} a{color:blue;} -> p,a{color:blue;}) + 'organize' => true, + // Combines color/style/width properties + // (border-style:dashed;border-color:black;border-width:4px; -> border:4px dashed black) + 'csw-combine' => true, + // Combines cue/pause properties + // (cue-before: url(before.au); cue-after: url(after.au) -> cue:url(before.au) url(after.au)) + 'auralcp-combine' => true, + // Combines margin/padding directionals + // (margin-top:10px;margin-right:5px;margin-bottom:4px;margin-left:1px; -> margin:10px 5px 4px 1px;) + 'mp-combine' => true, + // Combines border directionals + // (border-top|right|bottom|left:1px solid black -> border:1px solid black) + 'border-combine' => true, + // Combines font properties + // (font-size:12pt; font-family: arial; -> font:12pt arial) + 'font-combine' => true, + // Combines background properties + // (background-color: black; background-image: url(bgimg.jpeg); -> background:black url(bgimg.jpeg)) + 'background-combine' => true, + // Combines list-style properties + // (list-style-type: round; list-style-position: outside -> list-style:round outside) + 'list-combine' => true, + // Combines border-radius properties + // { + // border-top-left-radius: 10px; + // border-top-right-radius: 10px; + // border-bottom-right-radius: 10px; + // border-bottom-left-radius: 10px; + // } + // -> { border-radius: 10px; } + 'border-radius-combine' => true, + // Removes the last semicolon of a property set + // ({margin: 2px; color: blue;} -> {margin: 2px; color: blue}) + 'unnecessary-semicolons' => true, + // Removes multiple declarations within the same rule set + 'rm-multi-define' => true, + // Adds all unknown blocks to the top of the output in a comment strip + // Purely for bug reporting, but also useful to know what isn't being handled + 'add-unknown' => true, + // Readibility of Compressed Output, Defaults to none + 'readability' => 0, + ); + + /** + * Modes are predefined sets of configuration for referencing. When creating a mode, all options are set to true, + * and the mode array defines which options are to be false + * + * @mode safe: Safe mode does zero combinations or organizing. It's the best mode if you use a lot of hacks + * @mode sane: Sane mode does most combinations(multiple long hand notations to single shorthand), + * --- but still keeps most declarations in their place + * @mode small: Small mode reorganizes the whole sheet, combines as much as it can, and will break most comment hacks + * @mode full: Full mode does everything small does, but also converts hex codes to their short color name alternatives + */ + private static $modes = array( + 'safe' => array( + 'color-hex2shortcolor' => false, + 'attr2selector' => false, + 'strict-id' => false, + 'organize' => false, + 'csw-combine' => false, + 'auralcp-combine' => false, + 'mp-combine' => false, + 'border-combine' => false, + 'font-combine' => false, + 'background-combine' => false, + 'list-combine' => false, + 'border-radius-combine' => false, + 'rm-multi-define' => false, + ), + 'sane' => array( + 'color-hex2shortcolor' => false, + 'strict-id' => false, + 'organize' => false, + 'font-combine' => false, + 'background-combine' => false, + 'list-combine' => false, + 'rm-multi-define' => false, + ), + 'small' => array( + 'color-hex2shortcolor' => false, + 'pseudo-space' => false, + ), + 'full' => array( + 'pseudo-space' => false, + ), + ); + + /** + * Readability Constants + * + * @param (int) READ_MAX: Maximum readability of output + * @param (int) READ_MED: Medium readability of output + * @param (int) READ_MIN: Minimal readability of output + * @param (int) READ_NONE: No readability of output (full compression into single line) + */ + const READ_MAX = 3; + const READ_MED = 2; + const READ_MIN = 1; + const READ_NONE = 0; + + /** + * Static Helpers + * + * @instance express: Use a separate instance from singleton access + * @instance instance: Saved instance of CSSCompression + * @param (array) instances: Array of stored instances + * @param (array) rjson: Comment removal before json decoding + */ + private static $express; + private static $instance; + private static $instances = array(); + private static $rjson = array( + 'patterns' => array( + "/^(.*?){/s", + "/(\t|\s)+\/\/.*/", + ), + 'replacements' => array( + '{', + '', + ), + ); + + /** + * Controller Instance + */ + private $Control; + + /** + * Builds the subclasses, runs the compression if css passed, and merges options + * + * @param (string) css: CSS to compress on initialization if needed + * @param (array) options: Array of preferences to override the defaults + */ + public function __construct( $css = NULL, $options = NULL ) { + $this->Control = new CSSCompression_Control( $this ); + + // Autorun against css passed + if ( $css ) { + // Allow passing options/mode only + if ( is_array( $css ) || array_key_exists( $css, self::$modes ) ) { + $this->Control->Option->merge( $css ); + } + else { + $this->Control->compress( $css, $options ); + } + } + // Merge passed options + else if ( $options ) { + $this->Control->Option->merge( $options ); + } + } + + /** + * (Proxy function) Control access to properties + * + * - Getting stats/_mode/css returns the current value of that property + * - Getting options will return the current full options array + * - Getting anything else returns that current value in the options array or NULL + * + * @param (string) name: Name of property that you want to access + */ + public function __get( $name ) { + return $this->Control->get( $name ); + } + + /** + * (Proxy function) The setter method only allows + * access to setting values in the options array + * + * @param (string) name: Key name of the option you want to set + * @param (mixed) value: Value of the option you want to set + */ + public function __set( $name, $value ) { + return $this->Control->set( $name, $value ); + } + + /** + * (Proxy function) Merges a predefined set options + * + * @param (string) mode: Name of mode to use. + */ + public function mode( $mode = NULL ) { + return $this->Control->Option->merge( $mode ); + } + + /** + * Creates a new mode, or overwrites existing mode + * + * @param (mixed) mode: Name of the mode, or array of modes + * @param (array) config: Configuration of the mode + */ + public static function modes( $mode = NULL, $config = NULL ) { + if ( $mode === NULL ) { + return self::$modes; + } + else if ( is_array( $mode ) ) { + return array_merge( self::$modes, $mode ); + } + else if ( $config === NULL ) { + return isset( self::$modes[$mode] ) ? self::$modes[$mode] : NULL; + } + else { + return self::$modes[$mode] = $config; + } + } + + /** + * (Proxy function) Maintainable access to the options array + * + * - Passing no arguments returns the entire options array + * - Passing a single name argument returns the value for the option + * - Passing an array will merge the options with the array passed, for object like extension + * - Passing both a name and value, sets the value to the name key, and returns the value + * + * @param (mixed) name: The key name of the option + * @param (mixed) value: Value to set the option + */ + public function option( $name = NULL, $value = NULL ) { + return $this->Control->Option->option( $name, $value ); + } + + /** + * (Proxy function) Run compression on the sheet passed. + * + * @param (string) css: Stylesheet to be compressed + * @param (mixed) options: Array of options or mode to use. + */ + public function compress( $css = NULL, $options = NULL ) { + return $this->Control->compress( $css, $options ); + } + + /** + * Static access for direct compression + * + * @param (string) css: Stylesheet to be compressed + * @param (mixed) options: Array of options or mode to use. + */ + public static function express( $css = NULL, $options = NULL ) { + if ( ! self::$express ) { + self::$express = new CSSCompression(); + } + + self::$express->reset(); + return self::$express->compress( $css, $options ); + } + + /** + * (Proxy Function) Cleans out compressor and it's subclasses to defaults + * + * @params none + */ + public function reset() { + return $this->Control->reset(); + } + + /** + * (Proxy Function) Cleans out class variables for next run + * + * @params none + */ + public function flush() { + return $this->Control->flush(); + } + + /** + * The Singleton access method (for those that want it) + * + * @param (string) name: Name of the stored instance + */ + public static function getInstance( $name = NULL ) { + if ( $name !== NULL ) { + if ( ! isset( self::$instances[$name] ) ) { + self::$instances[$name] = new self; + } + + return self::$instances[$name]; + } + else if ( ! self::$instance ) { + self::$instance = new self; + } + + return self::$instance; + } + + /** + * Reads JOSN based files, strips comments and converts to array + * + * @param (string) file: Filename + */ + public static function getJSON( $file ) { + // Assume helper file if full path not given + $file = $file[0] == '/' ? $file : dirname(__FILE__) . '/helpers/' . $file; + + // Strip comments + $json = preg_replace( self::$rjson['patterns'], self::$rjson['replacements'], file_get_contents( $file ) ); + + // Decode json + $json = json_decode( $json, true ); + + // Check for errors + if ( $json === NULL ) { + $e = ''; + // JSON Errors, taken directly from http://php.net/manual/en/function.json-last-error.php + switch ( json_last_error() ) { + case JSON_ERROR_NONE: + $e = 'No error has occurred'; + break; + case JSON_ERROR_DEPTH: + $e = 'The maximum stack depth has been exceeded'; + break; + case JSON_ERROR_CTRL_CHAR: + $e = 'Control character error, possibly incorrectly encoded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $e = 'Invalid or malformed JSON'; + break; + case JSON_ERROR_SYNTAX: + $e = 'Syntax error'; + break; + case JSON_ERROR_UTF8: + $e = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $e = 'Unknown JSON Error'; + break; + } + + throw new CSSCompression_Exception( "JSON Error in $file: $e" ); + } + + // Good to go + return $json; + } + + /** + * Backdoor access to subclasses + * ONLY FOR DEVELOPMENT/TESTING. + * + * @param (string) class: Name of the focus class + * @param (string) method: Method function to call + * @param (array) args: Array of arguments to pass in + */ + public function access( $class = NULL, $method = NULL, $args = NULL ) { + if ( ! self::DEV ) { + throw new CSSCompression_Exception( "CSSCompression is not in development mode." ); + } + else if ( $class === NULL || $method === NULL || $args === NULL ) { + throw new CSSCompression_Exception( "Invalid Access Call." ); + } + else if ( ! is_array( $args ) ) { + throw new CSSCompression_Exception( "Expecting array of arguments." ); + } + + return $this->Control->access( $class, $method, $args ); + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-colors.json b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-colors.json new file mode 100644 index 0000000..49b392c --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-colors.json @@ -0,0 +1,39 @@ +/** + * CSS Compressor [VERSION] + * [DATE] + * Corey Hart @ http://www.codenothing.com + */ +{ + "#f0ffff":"azure", + "#f5f5dc":"beige", + "#ffe4c4":"bisque", + "#a52a2a":"brown", + "#ff7f50":"coral", + "#ffd700":"gold", + "#808080":"gray", + "#008000":"green", + "#4b0082":"indigo", + "#fffff0":"ivory", + "#f0e68c":"khaki", + "#faf0e6":"linen", + "#800000":"maroon", + "#000080":"navy", + "#808000":"olive", + "#ffa500":"orange", + "#da70d6":"orchid", + "#cd853f":"peru", + "#ffc0cb":"pink", + "#dda0dd":"plum", + "#800080":"purple", + "#ff0000":"red", + "#fa8072":"salmon", + "#a0522d":"sienna", + "#c0c0c0":"silver", + "#fffafa":"snow", + "#d2b48c":"tan", + "#008080":"teal", + "#ff6347":"tomato", + "#ee82ee":"violet", + "#f5deb3":"wheat", + "#f00":"red" +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-safe.json b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-safe.json new file mode 100644 index 0000000..cb8040f --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/hex2short-safe.json @@ -0,0 +1,16 @@ +/** + * CSS Compressor [VERSION] + * [DATE] + * Corey Hart @ http://www.codenothing.com + */ +{ + "#808080":"gray", + "#008000":"green", + "#800000":"maroon", + "#000080":"navy", + "#808000":"olive", + "#ff0000":"red", + "#c0c0c0":"silver", + "#008080":"teal", + "#f00":"red" +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/long2hex-colors.json b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/long2hex-colors.json new file mode 100644 index 0000000..a4b78cd --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/helpers/long2hex-colors.json @@ -0,0 +1,126 @@ +/** + * CSS Compressor [VERSION] + * [DATE] + * Corey Hart @ http://www.codenothing.com + */ +{ + "aliceblue":"#f0f8ff", + "antiquewhite":"#faebd7", + "aquamarine":"#7fffd4", + "bisque":"#ffe4c4", + "black":"#000000", + "blanchedalmond":"#ffebcd", + "blueviolet":"#8a2be2", + "burlywood":"#deb887", + "cadetblue":"#5f9ea0", + "chartreuse":"#7fff00", + "chocolate":"#d2691e", + "coral":"#ff7f50", + "cornflowerblue":"#6495ed", + "cornsilk":"#fff8dc", + "crimson":"#dc143c", + "cyan":"#00ffff", + "darkblue":"#00008b", + "darkcyan":"#008b8b", + "darkgoldenrod":"#b8860b", + "darkgray":"#a9a9a9", + "darkgreen":"#006400", + "darkkhaki":"#bdb76b", + "darkmagenta":"#8b008b", + "darkolivegreen":"#556b2f", + "darkorange":"#ff8c00", + "darkorchid":"#9932cc", + "darkred":"#8b0000", + "darksalmon":"#e9967a", + "darkseagreen":"#8fbc8f", + "darkslateblue":"#483d8b", + "darkslategray":"#2f4f4f", + "darkturquoise":"#00ced1", + "darkviolet":"#9400d3", + "deeppink":"#ff1493", + "deepskyblue":"#00bfff", + "dimgray":"#696969", + "dodgerblue":"#1e90ff", + "firebrick":"#b22222", + "floralwhite":"#fffaf0", + "forestgreen":"#228b22", + "fuchsia":"#ff00ff", + "gainsboro":"#dcdcdc", + "ghostwhite":"#f8f8ff", + "goldenrod":"#daa520", + "greenyellow":"#adff2f", + "honeydew":"#f0fff0", + "hotpink":"#ff69b4", + "indianred ":"#cd5c5c", + "indigo ":"#4b0082", + "lavender":"#e6e6fa", + "lavenderblush":"#fff0f5", + "lawngreen":"#7cfc00", + "lemonchiffon":"#fffacd", + "lightblue":"#add8e6", + "lightcoral":"#f08080", + "lightcyan":"#e0ffff", + "lightgoldenrodyellow":"#fafad2", + "lightgrey":"#d3d3d3", + "lightgreen":"#90ee90", + "lightpink":"#ffb6c1", + "lightsalmon":"#ffa07a", + "lightseagreen":"#20b2aa", + "lightskyblue":"#87cefa", + "lightslategray":"#778899", + "lightsteelblue":"#b0c4de", + "lightyellow":"#ffffe0", + "lime":"#00ff00", + "limegreen":"#32cd32", + "magenta":"#ff00ff", + "maroon":"#800000", + "mediumaquamarine":"#66cdaa", + "mediumblue":"#0000cd", + "mediumorchid":"#ba55d3", + "mediumpurple":"#9370d8", + "mediumseagreen":"#3cb371", + "mediumslateblue":"#7b68ee", + "mediumspringgreen":"#00fa9a", + "mediumturquoise":"#48d1cc", + "mediumvioletred":"#c71585", + "midnightblue":"#191970", + "mintcream":"#f5fffa", + "mistyrose":"#ffe4e1", + "moccasin":"#ffe4b5", + "navajowhite":"#ffdead", + "oldlace":"#fdf5e6", + "olivedrab":"#6b8e23", + "orange":"#ffa500", + "orangered":"#ff4500", + "orchid":"#da70d6", + "palegoldenrod":"#eee8aa", + "palegreen":"#98fb98", + "paleturquoise":"#afeeee", + "palevioletred":"#d87093", + "papayawhip":"#ffefd5", + "peachpuff":"#ffdab9", + "powderblue":"#b0e0e6", + "purple":"#800080", + "rosybrown":"#bc8f8f", + "royalblue":"#4169e1", + "saddlebrown":"#8b4513", + "salmon":"#fa8072", + "sandybrown":"#f4a460", + "seagreen":"#2e8b57", + "seashell":"#fff5ee", + "sienna":"#a0522d", + "silver":"#c0c0c0", + "skyblue":"#87ceeb", + "slateblue":"#6a5acd", + "slategray":"#708090", + "springgreen":"#00ff7f", + "steelblue":"#4682b4", + "thistle":"#d8bfd8", + "tomato":"#ff6347", + "turquoise":"#40e0d0", + "violet":"#ee82ee", + "white":"#ffffff", + "whitesmoke":"#f5f5f5", + "yellow":"#ffff00", + "yellowgreen":"#9acd32" +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Cleanup.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Cleanup.inc new file mode 100644 index 0000000..3036408 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Cleanup.inc @@ -0,0 +1,218 @@ + array( + "\\:", + "\\;", + "\\}", + "\\{", + "\\@", + "\\!", + "\\,", + "\\>", + "\\+", + "\\~", + "\\/", + "\\*", + "\\.", + "\\=", + "\\#", + "\\r", + "\\n", + "\\t", + "\\ ", + ), + 'replace' => array( + ":", + ";", + "}", + "{", + "@", + "!", + ",", + ">", + "+", + "~", + "/", + "*", + ".", + "=", + "#", + "\r", + "\n", + "\t", + " ", + ), + ); + + /** + * Build the token regex based on defined token + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->token = CSSCompression::TOKEN; + $this->options = &$control->Option->options; + + // Have to build the token regexs after initialization + $this->rtoken = "/($this->token)(.*?)($this->token)/"; + array_push( $this->rescape, $this->rtoken ); + } + + /** + * Central cleanup process, removes all injections + * + * @param (array) selectors: Array of selectors + * @param (array) details: Array of details + */ + public function cleanup( &$selectors, &$details ) { + foreach ( $details as $i => &$value ) { + // Auto skip sections + if ( isset( $selectors[$i] ) && strpos( $selectors[$i], $this->token ) === 0 ) { + continue; + } + + // Removing dupes + if ( $this->options['rm-multi-define'] ) { + $value = $this->removeMultipleDefinitions( $value ); + } + + $value = $this->removeUnnecessarySemicolon( $value ); + } + + return array($selectors, $details); + } + + /** + * Removes '\' from possible splitter characters in URLs + * + * @param (string) css: Full css sheet + */ + public function removeInjections( $css ) { + // Remove escaped characters + foreach ( $this->rescape as $regex ) { + $pos = 0; + while ( preg_match( $regex, $css, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $value = $match[1][0] + . str_replace( $this->escaped['search'], $this->escaped['replace'], $match[2][0] ) + . $match[3][0]; + $css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) ); + $pos = $match[0][1] + strlen( $value ) + 1; + } + } + + // Remove token injections + $pos = 0; + while ( preg_match( $this->rtoken, $css, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $value = $match[2][0]; + $id = substr( $css, $match[0][1] - 4, 4 ) == '[id=' ? true : false; + $class = substr( $css, $match[0][1] - 7, 7 ) == '[class=' ? true : false; + + if ( preg_match( $this->rspace, $value ) || ( ! $id && ! $class ) ) { + $quote = preg_match( $this->rquote, $value ) ? "\"" : "'"; + $value = "$quote$value$quote"; + $css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) ); + $pos = $match[0][1] + strlen( $value ) + 1; + } + else { + $css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) ); + $pos = $match[0][1] + strlen( $value ) + 1; + } + } + + return $css; + } + + /** + * Removes multiple definitions that were created during compression + * + * @param (string) val: CSS Selector Properties + */ + private function removeMultipleDefinitions( $val = '' ) { + $storage = array(); + $arr = preg_split( $this->rsemicolon, $val ); + + foreach ( $arr as $x ) { + if ( $x ) { + list( $a, $b ) = preg_split( $this->rcolon, $x, 2 ); + $storage[$a] = $b; + } + } + + if ( $storage ) { + $val = ''; + foreach ( $storage as $x => $y ) { + $val .= "$x:$y;"; + } + } + + // Return converted val + return $val; + } + + /** + * Removes last semicolons on the final property of a set + * + * @param (string) value: rule set + */ + private function removeUnnecessarySemicolon( $value ) { + return preg_replace( $this->rsemi, '', $value ); + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + if ( $method == 'cleanup' ) { + return $this->cleanup( $args[0], $args[1] ); + } + else { + return call_user_func_array( array($this, $method), $args ); + } + } + else { + throw new CSSCompression_Exception( "Unknown method in Cleanup Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Color.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Color.inc new file mode 100644 index 0000000..3f6cac9 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Color.inc @@ -0,0 +1,196 @@ + 'long2hex-colors.json', + 'hex2short' => 'hex2short-colors.json', + 'hex2short_safe' => 'hex2short-safe.json', + ); + + /** + * Stash a reference to the controller on each instantiation + * and install conversion helpers + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->options = &$control->Option->options; + + if ( ! self::$color2hex ) { + foreach ( self::$files as $v => $file ) { + self::$$v = CSSCompression::getJSON( $file ); + } + } + } + + /** + * Central handler for all color conversions. + * + * @param (string) val: Color to be parsed + */ + public function color( $val ) { + // Converts rgb values to hex codes + if ( $this->options['color-rgb2hex'] ) { + $val = $this->rgb2hex( $val ); + } + + // Convert long color names to hex codes + if ( $this->options['color-long2hex'] ) { + $val = $this->color2hex( $val ); + } + + // Ensure all hex codes are lowercase + if ( preg_match( $this->rhex, $val ) ) { + $val = strtolower( $val ); + } + + // Convert large hex codes to small codes + if ( $this->options['color-hex2shorthex'] ) { + $val = $this->hex2short( $val ); + } + + // Convert 6 digit hex codes to short color names + if ( $this->options['color-hex2shortcolor'] ) { + $val = $this->hex2color( $val ); + } + + // Convert safe css level1 color names + if ( $this->options['color-hex2safe'] ) { + $val = $this->hex2safe( $val ); + } + + return $val; + } + + /** + * Converts rgb values to hex codes + * + * @param (string) val: Color to be converted + */ + private function rgb2hex( $val ) { + if ( ! preg_match( $this->rrgb, $val, $match ) ) { + return $val; + } + + // locals + $hex = '0123456789abcdef'; + $str = explode( ',', $match[1] ); + $new = ''; + + // Incase rgb was defined with single val + if ( ! $str ) { + $str = array($match[1]); + } + + foreach ( $str as $x ) { + $x = strpos( $x, '%' ) !== false ? intval( ( intval( $x ) / 100 ) * 255 ) : intval( $x ); + + if ( $x > 255 ) { + $x = 255; + } + + if ( $x < 0 ) { + $x = 0; + } + + $new .= $hex[( $x - $x % 16 ) / 16]; + $new .= $hex[$x % 16]; + } + + // Repeat hex code to complete 6 digit hex requirement for single definitions + if ( count( $str ) == 1 ) { + $new .= $new . $new; + } + + // Replace with hex value + return "#$new"; + } + + /** + * Convert long color names to hex codes + * + * @param (string) val: Color to be converted + */ + private function color2hex( $val ) { + return isset( self::$color2hex[$val] ) ? self::$color2hex[$val] : $val; + } + + /** + * Convert large hex codes to small codes + * + * @param (string) val: Hex to be shortened + */ + private function hex2short( $val ) { + if ( ! preg_match( $this->rfullhex, $val, $match ) ) { + return $val; + } + + // See if we can convert to 3 char hex + $hex = $match[1]; + if ( $hex[0] == $hex[1] && $hex[2] == $hex[3] && $hex[4] == $hex[5] ) { + $val = '#' . $hex[0] . $hex[2] . $hex[4]; + } + + return $val; + } + + /** + * Convert large hex codes to small codes + * + * @param (string) val: Color to be converted + */ + private function hex2color( $val ) { + return isset( self::$hex2short[$val] ) ? self::$hex2short[$val] : $val; + } + + /** + * Convert large hex codes to small codes + * + * @param (string) val: Color to be converted + */ + private function hex2safe( $val ) { + return isset( self::$hex2short_safe[$val] ) ? self::$hex2short_safe[$val] : $val; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Color Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine.inc new file mode 100644 index 0000000..2629821 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine.inc @@ -0,0 +1,188 @@ + 'BorderOutline', + 'border-radius-combine' => 'BorderRadius', + 'border-combine' => 'Border', + 'mp-combine' => 'MarginPadding', + 'background-combine' => 'Background', + 'auralcp-combine' => 'Aural', + 'font-combine' => 'Font', + 'list-combine' => 'List', + ); + + /** + * Sub Comination Classes + * + * @class BorderOutline: Handles Color/Style/With combinations of border/outline properties + * @class BorderRadius: Handles border-radius combinations + * @class Border: Handles normal border combinations + * @class MarginPadding: Handles margin/padding combinations + * @class Background: Handles background combinations + * @class Aural: Handles aural combinations + * @class Font: Handles font combinations + * @class List: Handles list combinations + * @param (array) subcombines: Array holding all subcombination classes + */ + public $BorderOutline; + public $BorderRadius; + public $Border; + public $MarginPadding; + public $Background; + public $Aural; + public $Font; + public $List; + private $subcombines = array( + 'BorderOutline', + 'BorderRadius', + 'Border', + 'MarginPadding', + 'Background', + 'Aural', + 'Font', + 'List', + ); + + /** + * Stash a reference to the controller on each instantiation + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->token = CSSCompression::TOKEN; + $this->options = &$control->Option->options; + + // Include classes if not already done so + if ( ! class_exists( "CSSCompression_Combine_Border", false ) ) { + $path = dirname(__FILE__) . '/Combine/'; + foreach ( $this->subcombines as $class ) { + require( $path . $class . '.inc' ); + } + } + + // Instantiate each sub combine + foreach ( $this->subcombines as $class ) { + $full = "CSSCompression_Combine_$class"; + $this->$class = new $full( $control, $this ); + } + } + + /** + * Reads through each detailed package and checks for cross defn combinations + * + * @param (array) selectors: Array of selectors + * @param (array) details: Array of details + */ + public function combine( &$selectors = array(), &$details = array() ) { + foreach ( $details as $i => &$value ) { + if ( isset( $selectors[$i] ) && strpos( $selectors[$i], $this->token ) === 0 ) { + continue; + } + + foreach ( $this->methods as $option => $class ) { + if ( $this->options[$option] ) { + $value = $this->$class->combine( $value ); + } + } + } + + return array($selectors, $details); + } + + /** + * Helper function to ensure flagged words don't get + * overridden + * + * @param (mixed) obj: Array/String of definitions to be checked + */ + public function checkUncombinables( $obj ) { + if ( is_array( $obj ) ) { + foreach ( $obj as $item ) { + if ( preg_match( $this->rimportant, $item ) ) { + return true; + } + } + return false; + } + else { + return preg_match( $this->rimportant, $obj ); + } + } + + /** + * Helper function to ensure all values of search array + * exist within the storage array + * + * @param (string) prop: CSS Property + * @param (array) storage: Array of definitions found + * @param (array) search: Array of definitions requred + */ + public function searchDefinitions( $prop, $storage, $search ) { + // Return if storage & search don't match + if ( count( $storage ) != count( $search ) ) { + return false; + } + + $str = "$prop:"; + foreach ( $search as $value ) { + if ( ! isset( $storage[$value] ) || $this->checkUncombinables( $storage[$value] ) ) { + return false; + } + $str .= $storage[$value] . ' '; + } + return trim( $str ) . ';'; + } + + /** + * Access to private methods for testing + * + * @param (string) subclass: Name of subclass to focus on + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $subclass, $method, $args ) { + if ( $subclass == 'Combine' ) { + if ( method_exists( $this, $method ) ) { + if ( $method == 'combine' ) { + return $this->combine( $args[0], $args[1] ); + } + else { + return call_user_func_array( array($this, $method), $args ); + } + } + else { + throw new CSSCompression_Exception( "Unknown method in Combine Class - " . $method ); + } + } + else if ( in_array( $subclass, $this->subcombines ) ) { + return $this->$subclass->access( $method, $args ); + } + else { + throw new CSSCompression_Exception( "Unknown Sub Combine Class - " . $subclass ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Aural.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Aural.inc new file mode 100644 index 0000000..08fc0b1 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Aural.inc @@ -0,0 +1,106 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines Aural properties (currently being depreciated in W3C Standards) + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + $storage = $this->storage( $val ); + $pos = 0; + + // Replace first occurance with it's prop, and remove all following occurances + while ( preg_match( $this->raural, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $prop = $match[2][0]; + if ( isset( $storage[$prop] ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $storage[$prop], $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $storage[$prop] ) - $colon - 1; + $storage[$prop] = ''; + } + else { + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + } + + // Return converted val + return $val; + } + + /** + * Builds a storage object for iteration + * + * @param (string) val: Rule Set + */ + private function storage( $val ) { + $storage = array(); + + // Find all possible occurences and build the replacement + $pos = 0; + while ( preg_match( $this->raural, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + if ( ! isset( $storage[$match[2][0]] ) ) { + $storage[$match[2][0]] = array($match[3][0] => $match[4][0]); + } + + // Override double written properties + $storage[$match[2][0]][$match[3][0]] = $match[4][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // Go through each tag for possible combination + foreach ( $storage as $tag => $arr ) { + // All three have to be defined + if ( count( $arr ) == 2 && ! $this->Combine->checkUncombinables( $arr ) ) { + $storage[$tag] = "$tag:" . $arr['before'] . ' ' . $arr['after'] . ';'; + } + else { + unset( $storage[$tag] ); + } + } + + return $storage; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Aural Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Background.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Background.inc new file mode 100644 index 0000000..2ec1e84 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Background.inc @@ -0,0 +1,104 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines multiple background props into single definition + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + $storage = array(); + + // Find all possible occurences and build the replacement + $pos = 0; + while ( preg_match( $this->rbackground, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $storage[$match[2][0]] = $match[3][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // Run background checks and get replacement str + foreach ( $this->groupings as $props ) { + if ( $replace = $this->Combine->searchDefinitions( 'background', $storage, $props ) ) { + break; + } + } + + // If replacement string found, run it on all declarations + if ( $replace ) { + $pos = 0; + while ( preg_match( $this->rbackground, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $replace ) - $colon - 1; + $replace = ''; + } + } + + // Return converted val + return $val; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Background Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Border.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Border.inc new file mode 100644 index 0000000..140d4c0 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Border.inc @@ -0,0 +1,95 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines multiple border properties into single definition + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + if ( ( $replace = $this->replace( $val ) ) === false ) { + return $val; + } + + // Rebuild the rule set with the combinations found + $pos = 0; + while ( preg_match( $this->rborder, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $replace ) - $colon - 1; + $replace = ''; + } + + // Return converted val + return $val; + } + + /** + * Builds a replacement string + * + * @param (string) val: Rule Set + */ + private function replace( $val ) { + $storage = array(); + + // Find all possible occurences and build the replacement + $pos = 0; + while ( preg_match( $this->rborder, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + // Override double written properties + $storage[$match[2][0]] = $match[3][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // All 4 have to be defined + if ( count( $storage ) == 4 && + $storage['top'] == $storage['bottom'] && + $storage['left'] == $storage['right'] && + $storage['top'] == $storage['right'] ) { + return "border:" . $storage['top'] . ';'; + } + + return false; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Border Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderOutline.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderOutline.inc new file mode 100644 index 0000000..c79551f --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderOutline.inc @@ -0,0 +1,106 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines color/style/width of border/outline properties + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + $storage = $this->storage( $val ); + $pos = 0; + + // Now rebuild the string replacing all instances + while ( preg_match( $this->rcsw, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $prop = $match[2][0]; + if ( isset( $storage[$prop] ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $storage[$prop], $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $storage[$prop] ) - $colon - 1; + $storage[$prop] = ''; + } + else { + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + } + + // Return converted val + return $val; + } + + /** + * Builds a storage object for iteration + * + * @param (string) val: Rule Set + */ + private function storage( $val ) { + $storage = array(); + $pos = 0; + + // Find all possible occurences and build the replacement + while ( preg_match( $this->rcsw, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + if ( ! isset( $storage[$match[2][0]] ) ) { + $storage[$match[2][0]] = array($match[3][0] => $match[4][0]); + } + + // Override double written properties + $storage[$match[2][0]][$match[3][0]] = $match[4][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // Go through each tag for possible combination + foreach ( $storage as $tag => $arr ) { + // All three have to be defined + if ( count( $arr ) == 3 && ! $this->Combine->checkUncombinables( $arr ) ) { + $storage[$tag] = "$tag:" . $arr['width'] . ' ' . $arr['style'] . ' ' . $arr['color'] . ';'; + } + else { + unset( $storage[$tag] ); + } + } + + return $storage; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in BorderOutline Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderRadius.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderRadius.inc new file mode 100644 index 0000000..d2bbc10 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/BorderRadius.inc @@ -0,0 +1,263 @@ + array( + 'mod' => '', + 'base' => "/(^|(? "/(^|(? array( + 'mod' => '-moz-', + 'base' => "/(^|(? "/(^|(? array( + 'mod' => '-webkit-', + 'base' => "/(^|(? "/(^|(?Control = $control; + $this->Combine = $combine; + } + + + /** + * Main handler to combine border-radii into a single rule + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + foreach ( $this->borderRadius as $regex ) { + $val = $this->fix( $val, $regex ); + } + + return $val; + } + + /** + * Does the actual combining + * + * @param (string) val: Rule Set + */ + private function fix( $val, $regex ) { + $val = $this->base( $val, $regex ); + $replace = $regex['mod']; + + // Storage builder + if ( ( $storage = $this->storage( $val, $regex ) ) === false ) { + return $val; + } + + // Setup horizontal/vertical radii + foreach ( $storage as $dir => &$config ) { + // Verticals are optional + if ( $dir == 'vertical' && ! $config['keep'] ) { + break; + } + // All 4 are the same + else if ( $config['top-left'] == $config['top-right'] && + $config['top-right'] == $config['bottom-right'] && + $config['bottom-right'] == $config['bottom-left'] ) { + $config['replace'] .= $config['top-left']; + } + // Opposites are the same + else if ( $config['top-left'] == $config['bottom-right'] && $config['top-right'] == $config['bottom-left'] ) { + $config['replace'] .= $config['top-left'] . ' ' . $config['top-right']; + } + // 3-point directional + else if ( $config['top-right'] == $config['bottom-left'] ) { + $config['replace'] .= $config['top-left'] . ' ' . $config['top-right'] . ' ' . $config['bottom-right']; + } + // none are the same, but can still use shorthand notation + else { + $config['replace'] .= $config['top-left'] . ' ' . $config['top-right'] . ' ' + . $config['bottom-right'] . ' ' . $config['bottom-left']; + } + } + + // Now rebuild the string replacing all instances of margin/padding if shorthand exists + $pos = 0; + $replace = $regex['mod'] . "border-radius:" . $storage['horizontal']['replace'] . $storage['vertical']['replace'] . ';'; + while ( preg_match( $regex['full'], $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $replace ) - $colon - 1; + $replace = ''; + } + + // Return converted val + return $val; + } + + /** + * Expands short handed border radius props for combination + * + * @param (string) val: Rule Set + */ + private function base( $val, $regex ) { + $pos = 0; + while ( preg_match( $regex['base'], $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $replace = ''; + $colon = strlen( $match[1][0] ); + $parts = preg_split( $this->rslash, trim( $match[2][0] ), 2 ); + $positions = array( + 'top-left' => 0, + 'top-right' => 0, + 'bottom-right' => 0, + 'bottom-left' => 0, + ); + $base = array( + 'horizontal' => array( + 'parts' => preg_split( $this->rspace, trim( $parts[0] ) ), + 'pos' => $positions, + ), + 'vertical' => array( + 'parts' => isset( $parts[1] ) ? preg_split( $this->rspace, trim( $parts[1] ) ) : '', + 'pos' => $positions, + ), + ); + + foreach ( $base as &$config ) { + // Skip uncombinables + if ( $this->Combine->checkUncombinables( $config['parts'] ) ) { + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + continue 2; + } + // Might not have verticals + else if ( $config['parts'] === '' ) { + continue; + } + + // Each position needs a value + switch ( count( $config['parts'] ) ) { + case 1: + $config['pos']['top-left'] = $config['pos']['top-right'] = $config['parts'][0]; + $config['pos']['bottom-left'] = $config['pos']['bottom-right'] = $config['parts'][0]; + break; + case 2: + $config['pos']['top-left'] = $config['pos']['bottom-right'] = $config['parts'][0]; + $config['pos']['bottom-left'] = $config['pos']['top-right'] = $config['parts'][1]; + break; + case 3: + $config['pos']['top-left'] = $config['parts'][0]; + $config['pos']['bottom-left'] = $config['pos']['top-right'] = $config['parts'][1]; + $config['pos']['bottom-right'] = $config['parts'][2]; + break; + case 4: + $config['pos']['top-left'] = $config['parts'][0]; + $config['pos']['top-right'] = $config['parts'][1]; + $config['pos']['bottom-right'] = $config['parts'][2]; + $config['pos']['bottom-left'] = $config['parts'][3]; + break; + default: + continue 2; + } + + } + + // Build the replacement + foreach ( $positions as $p => $v ) { + if ( $regex['mod'] == '-moz-' ) { + $replace .= "-moz-border-radius-" . preg_replace( "/-/", '', $p ) . ":" + . $base['horizontal']['pos'][$p] + . ( $base['vertical']['parts'] === '' ? '' : ' ' . $base['vertical']['pos'][$p] ) + . ';'; + } + else { + $replace .= $regex['mod'] . "border-$p-radius:" + . $base['horizontal']['pos'][$p] + . ( $base['vertical']['parts'] === '' ? '' : ' ' . $base['vertical']['pos'][$p] ) + . ';'; + } + } + $pos += strlen( $replace ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + } + + return $val; + } + + /** + * Builds the storage object for border radius props + * + * @param (string) val: Rule Set + * @param (array) regex: Current border radius type checking props + */ + private function storage( $val, $regex ) { + $storage = array( + 'horizontal' => array('replace' => ''), + 'vertical' => array( + 'replace' => '', + 'keep' => false, + ), + ); + + // Find all possible occurences of this border-radius type and mark their directional value + $pos = 0; + while ( preg_match( $regex['full'], $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + $parts = preg_split( $this->rspace, $match[4][0], 2 ); + $storage['horizontal'][$match[2][0] . '-' . $match[3][0]] = trim( $parts[0] ); + if ( isset( $parts[1] ) ) { + $storage['vertical'][$match[2][0] . '-' . $match[3][0]] = trim( $parts[1] ); + $storage['vertical']['keep'] = true; + $storage['vertical']['replace'] = '/'; + } + else { + $storage['vertical'][$match[2][0] . '-' . $match[3][0]] = '0'; + } + } + + // Only combine if all 4 definitions are found (5 including replace) + if ( count( $storage['horizontal'] ) != 5 || + $this->Combine->checkUncombinables( $storage['horizontal'] ) || + $this->Combine->checkUncombinables( $storage['vertical'] ) ) { + return false; + } + + return $storage; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in BorderRadius Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Font.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Font.inc new file mode 100644 index 0000000..9d9b178 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/Font.inc @@ -0,0 +1,122 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines multiple font-definitions into single definition + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + $storage = $this->storage( $val ); + + // Loop through each property check and see if they can be replaced + foreach ( $this->groupings as $props ) { + if ( $replace = $this->Combine->searchDefinitions( 'font', $storage, $props ) ) { + break; + } + } + + // If replacement string found, run it on all declarations + if ( $replace ) { + $pos = 0; + while ( preg_match( $this->rfont, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + if ( ! isset( $storage['line-height'] ) && stripos( $match[0][0], 'line-height') === 0 ) { + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + continue; + } + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $replace ) - $colon - 1; + $replace = ''; + } + } + + // Return converted val + return $val; + } + + /** + * Builds a storage object for iteration + * + * @param (string) val: Rule Set + */ + private function storage( $val ) { + $storage = array(); + + // Find all possible occurences and build the replacement + $pos = 0; + while ( preg_match( $this->rfont, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $storage[$match[2][0] . '-' . $match[3][0]] = $match[4][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // Combine font-size & line-height if possible + if ( isset( $storage['font-size'] ) && isset( $storage['line-height'] ) ) { + $storage['size/height'] = $storage['font-size'] . '/' . $storage['line-height']; + unset( $storage['font-size'], $storage['line-height'] ); + } + + return $storage; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Font Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/List.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/List.inc new file mode 100644 index 0000000..d50b16d --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/List.inc @@ -0,0 +1,101 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines multiple list style props into single definition + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + // If replacement string found, run it on all declarations + if ( ( $replace = $this->replace( $val ) ) !== false ) { + $pos = 0; + while ( preg_match( $this->rlist, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $replace ) - $colon - 1; + $replace = ''; + } + } + + // Return converted val + return $val; + } + + /** + * Build the replacement string for list props + * + * @param (string) val: Rule Set + */ + private function replace( $val ) { + $storage = array(); + $pos = 0; + + // Find all possible occurences and build the replacement + while ( preg_match( $this->rlist, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $storage[$match[2][0]] = $match[3][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // Run background checks and get replacement str + foreach ( $this->groupings as $props ) { + if ( $replace = $this->Combine->searchDefinitions( 'list-style', $storage, $props ) ) { + return $replace; + } + } + + return false; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in List Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/MarginPadding.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/MarginPadding.inc new file mode 100644 index 0000000..966e585 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Combine/MarginPadding.inc @@ -0,0 +1,187 @@ +Control = $control; + $this->Combine = $combine; + } + + /** + * Combines multiple directional properties of + * margin/padding into single definition. + * + * @param (string) val: Rule Set + */ + public function combine( $val ) { + $val = $this->expand( $val ); + $storage = $this->storage( $val ); + $pos = 0; + + // Now rebuild the string replacing all instances of margin/padding if shorthand exists + while ( preg_match( $this->rmp, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $prop = $match[2][0]; + if ( isset( $storage[$prop] ) ) { + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $storage[$prop], $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $storage[$prop] ) - $colon - 1; + $storage[$prop] = ''; + } + else { + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + } + + // Return converted val + return $val; + } + + /** + * Build the storage object for iteration + * + * @param (string) val: Rule Set + */ + private function storage( $val ) { + $storage = array(); + $pos = 0; + + // Find all possible occurences of margin/padding and mark their directional value + while ( preg_match( $this->rmp, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + if ( ! isset( $storage[$match[2][0]] ) ) { + $storage[$match[2][0]] = array($match[3][0] => $match[4][0]); + } + + // Override double written properties + $storage[$match[2][0]][$match[3][0]] = $match[4][0]; + $pos = $match[0][1] + strlen( $match[0][0] ) - 1; + } + + // Go through each tag for possible combination + foreach ( $storage as $tag => $arr ) { + // Only combine if all 4 definitions are found + if ( count( $arr ) == 4 && ! $this->Combine->checkUncombinables( $arr ) ) { + // All 4 are the same + if ( $arr['top'] == $arr['bottom'] && $arr['left'] == $arr['right'] && $arr['top'] == $arr['left'] ) { + $storage[$tag] = "$tag:" . $arr['top'] . ';'; + } + // Opposites are the same + else if ( $arr['top'] == $arr['bottom'] && $arr['left'] == $arr['right'] ) { + $storage[$tag] = "$tag:" . $arr['top'] . ' ' . $arr['left'] . ';'; + } + // 3-point directional + else if ( $arr['right'] == $arr['left'] ) { + $storage[$tag] = "$tag:" . $arr['top'] . ' ' . $arr['right'] . ' ' . $arr['bottom'] . ';'; + } + // none are the same, but can still use shorthand notation + else { + $storage[$tag] = "$tag:" . $arr['top'] . ' ' . $arr['right'] . ' ' . $arr['bottom'] . ' ' . $arr['left'] . ';'; + } + } + else { + unset( $storage[$tag] ); + } + } + + return $storage; + } + + /** + * Explodes shorthanded margin/padding properties for later combination + * + * @param (string) val: Rule set + */ + private function expand( $val ) { + $pos = 0; + while ( preg_match( $this->rmpbase, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $replace = ''; + $prop = $match[2][0]; + $value = preg_split( $this->rspace, trim( $match[3][0] ) ); + $positions = array( + 'top' => 0, + 'right' => 0, + 'bottom' => 0, + 'left' => 0, + ); + + // Skip uncombinables + if ( $this->Combine->checkUncombinables( $value ) ) { + $pos = $match[0][1] + strlen( $match[0][0] ); + continue; + } + + // Each position needs a value + switch ( count( $value ) ) { + case 1: + $positions['top'] = $positions['right'] = $positions['bottom'] = $positions['left'] = $value[0]; + break; + case 2: + $positions['top'] = $positions['bottom'] = $value[0]; + $positions['right'] = $positions['left'] = $value[1]; + break; + case 3: + $positions['top'] = $value[0]; + $positions['right'] = $positions['left'] = $value[1]; + $positions['bottom'] = $value[2]; + break; + case 4: + $positions['top'] = $value[0]; + $positions['right'] = $value[1]; + $positions['bottom'] = $value[2]; + $positions['left'] = $value[3]; + break; + default: + continue; + } + + // Build the replacement + foreach ( $positions as $p => $v ) { + $replace .= "$prop-$p:$v;"; + } + $colon = strlen( $match[1][0] ); + $val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon ); + $pos = $match[0][1] + strlen( $replace ) - $colon - 1; + } + + return $val; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in MarginPadding Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Compress.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Compress.inc new file mode 100644 index 0000000..b1c3853 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Compress.inc @@ -0,0 +1,209 @@ +Control = $control; + $this->options = &$control->Option->options; + $this->stats = &$control->stats; + + foreach ( $this->others as $class ) { + $this->$class = $control->$class; + } + } + + /** + * Centralized function to run css compression. + * + * @param (string) css: Stylesheet to compresss + */ + public function compress( $css ) { + $setup = $this->setup( $css ); + $setup = $this->rulesets( $setup ); + $css = $this->readability( $setup ); + + // Attach plea to top of page with unknown blocks + if ( $this->options['add-unknown'] && count( $setup['unknown'] ) ) { + $css = "/*\nThere are unknown blocks in the sheet, please please please open an issue with your sheet attached to it:\n" + . "https://github.com/codenothing/css-compressor/issues\n" + . "Thank You --\n\n" + . implode( "\n", $setup['unknown'] ) + . "\n*/\n" + . $css; + } + + // Mark final file size + $this->stats['after']['size'] = strlen( $css = trim( $css ) ); + + // Return compressed css + return $css; + } + + /** + * Runs css through initial setup handlers + * + * @param (string) css: Sheet to compress + */ + private function setup( $css ) { + // Initial stats + $this->stats['before']['time'] = microtime( true ); + $this->stats['before']['size'] = strlen( $css ); + + // Initial trimming + $css = $this->Trim->trim( $css ); + + // Do a little tokenizing, compress each property individually + $setup = $this->Setup->setup( $css ); + + // Mark number of selectors pre-combine + $this->stats['before']['selectors'] = count( $setup['selectors'] ); + + return $setup; + } + + /** + * Focus compressions on each rule set + * + * @param (array) setup: Array containing selectors and rule sets + */ + private function rulesets( $setup ) { + // Do selector specific compressions + $this->Selectors->selectors( $setup['selectors'] ); + + // Look at each group of properties as a whole, and compress/combine similiar definitions + $this->Combine->combine( $setup['selectors'], $setup['details'] ); + + // If order isn't important, run comination functions before and after compressions to catch all instances + // Be sure to prune before hand for higher chance of matching + if ( $this->options['organize'] ) { + $this->Cleanup->cleanup( $setup['selectors'], $setup['details'] ); + $this->Organize->organize( $setup['selectors'], $setup['details'] ); + $this->Combine->combine( $setup['selectors'], $setup['details'] ); + } + + // Do final maintenace work, remove injected property/values + $this->Cleanup->cleanup( $setup['selectors'], $setup['details'] ); + + // Run final counters before full cleanup + $this->finalCount( $setup['selectors'], $setup['details'] ); + + return $setup; + } + + /** + * Runs final counts on selectors and props + * + * @param (array) selectors: Selector rules + * @param (array) details: Rule sets + */ + private function finalCount( $selectors, $details ) { + // Selectors and props + $this->stats['after']['selectors'] = count( $selectors ); + foreach ( $details as $item ) { + $props = preg_split( $this->rsemicolon, $item ); + + // Make sure count is true + foreach ( $props as $k => $v ) { + if ( ! isset( $v ) || $v == '' ) { + unset( $props[$k] ); + } + } + $this->stats['after']['props'] += count( $props ); + } + + // Final count for stats + $this->stats['after']['time'] = microtime( true ); + } + + /** + * Formats the compressed rule sets into a stylesheet + * + * @param (array) setup: Array containing selectors and rule sets + */ + private function readability( $setup ) { + // Format css to users preference + $css = $this->Format->readability( $this->options['readability'], $setup['selectors'], $setup['details'] ); + + // Intros + foreach ( $setup as $value ) { + if ( $value && is_string( $value ) ) { + $css = $value . $css; + } + } + + // Remove escapables + $css = $this->Cleanup->removeInjections( $css ); + + return $css; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Compress Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Control.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Control.inc new file mode 100644 index 0000000..529607d --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Control.inc @@ -0,0 +1,233 @@ +CSSCompression = $CSSCompression; + + // Load all subclasses on demand + if ( ! class_exists( "CSSCompression_Option", false ) ) { + $path = dirname(__FILE__) . '/'; + foreach ( $this->subclasses as $class ) { + require( $path . $class . '.inc' ); + } + } + + // Initialize each subclass + foreach ( $this->subclasses as $class ) { + $full = "CSSCompression_$class"; + $this->$class = new $full( $this ); + } + } + + /** + * Control access to properties + * + * - Getting stats/mode/css returns the current value of that property + * - Getting options will return the current full options array + * - Getting anything else returns that current value in the options array or NULL + * + * @param (string) name: Name of property that you want to access + */ + public function get( $name ) { + if ( in_array( $name, $this->getters ) ) { + return $this->$name; + } + else if ( $name == 'options' ) { + return $this->Option->options; + } + else { + return $this->Option->option( $name ); + } + } + + /** + * The setter method only allows access to setting values in the options array + * + * @param (string) name: Key name of the option you want to set + * @param (mixed) value: Value of the option you want to set + */ + public function set( $name, $value ) { + // Allow for passing array of options to merge into current ones + if ( $name === 'options' && is_array( $value ) ) { + return $this->Option->merge( $value ); + } + else if ( isset( CSSCompression::$defaults[$name] ) ) { + return $this->Option->option( $name, $value ); + } + else { + throw new CSSCompression_Exception( "Invalid Private Access to $name in CSSCompression." ); + } + } + + /** + * Merges a predefined set options + * + * @param (string) mode: Name of mode to use. + */ + public function mode( $mode = NULL ) { + return $this->Options->merge( $mode ); + } + + /** + * Resets options to their defaults, and flushes out variables + * + * @params none + */ + public function reset() { + $this->Option->reset(); + return $this->flush(); + } + + /** + * Cleans out class variables for next run + * + * @params none + */ + public function flush() { + $this->css = ''; + $this->stats = array( + 'before' => array( + 'props' => 0, + 'selectors' => 0, + 'size' => 0, + 'time' => 0, + ), + 'after' => array( + 'props' => 0, + 'selectors' => 0, + 'size' => 0, + 'time' => 0, + ), + ); + + return true; + } + + /** + * Proxy to run Compression on the sheet passed. Handle options here. + * + * @param (string) css: Stylesheet to be compressed + * @param (mixed) options: Array of options or mode to use. + */ + public function compress( $css = NULL, $options = NULL ) { + // Flush out old stats and variables + $this->flush(); + + // If no additional options, just run compression + if ( $options === NULL ) { + return $this->css = $this->Compress->compress( $css ); + } + + // Store old params + $old = $this->mode == '__custom' ? $this->Option->option() : $this->mode; + $readability = $this->Option->option( 'readability' ); + + // Compress with new set of options + $this->Option->merge( $options ); + $css = $this->Compress->compress( $css ); + + // Reset original options + $this->reset(); + $this->Option->merge( $old ); + + // Return the compressed css + return $this->css = $css; + } + + /** + * Backdoor access to subclasses + * ONLY FOR DEVELOPMENT/TESTING. + * + * @param (string) class: Name of the focus class + * @param (array) config: Contains name reference and test arguments + */ + public function access( $class, $method, $args ) { + if ( $class == 'Control' ) { + return call_user_func_array( array($class, $method), $args ); + } + else if ( strpos( $class, '.' ) !== false ) { + $parts = explode( '.', $class ); + $class = $parts[0]; + $subclass = $parts[1]; + return $this->$class->access( $subclass, $method, $args ); + } + else if ( in_array( $class, $this->subclasses ) ) { + return $this->$class->access( $method, $args ); + } + else { + throw new CSSCompression_Exception( "Unknown Class Access - " . $class ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Exception.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Exception.inc new file mode 100644 index 0000000..1345e25 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Exception.inc @@ -0,0 +1,29 @@ +code . "] " . $this->message . "\n"; + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Format.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Format.inc new file mode 100644 index 0000000..0f96dd7 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Format.inc @@ -0,0 +1,184 @@ + 'maximum', + CSSCompression::READ_MED => 'medium', + CSSCompression::READ_MIN => 'minimum', + CSSCompression::READ_NONE => 'none', + ); + + /** + * Stash a reference to the controller on each instantiation + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->token = CSSCompression::TOKEN; + $this->options = &$control->Option->options; + } + + + /** + * Reformats compressed CSS into specified format + * + * @param (int) readability: Readability level of compressed output + * @param (array) selectors: Array of selectors + * @param (array) details: Array of declarations + */ + public function readability( $readability = CSSCompression::READ_NONE, $selectors = array(), $details = array() ) { + if ( isset( $this->readability[$readability] ) ) { + $fn = $this->readability[$readability]; + return trim( $this->$fn( $selectors, $details ) ); + } + else { + return 'Invalid Readability Value'; + } + } + + /** + * Returns maxium readability, breaking on every selector, brace, and property + * + * @param (array) selectors: Array of selectors + * @param (array) details: Array of declarations + */ + private function maximum( $selectors, $details ) { + $css = ''; + foreach ( $selectors as $k => $v ) { + if ( strpos( $v, $this->token ) === 0 ) { + $css .= substr( $v, strlen( $this->token ) ); + $css .= $details[$k]; + continue; + } + else if ( ! $details[$k] || trim( $details[$k] ) == '' ) { + continue; + } + + $v = str_replace( '>', ' > ', $v ); + $v = str_replace( '+', ' + ', $v ); + $v = str_replace( ',', ', ', $v ); + $css .= "$v {\n"; + $arr = preg_split( $this->rsemicolon, $details[$k] ); + + foreach ( $arr as $item ) { + if ( ! $item ) { + continue; + } + + list( $prop, $val ) = preg_split( $this->rcolon, $item, 2 ); + $css .= "\t$prop: $val;\n"; + } + + // Kill that last semicolon at users request + if ( $this->options['unnecessary-semicolons'] ) { + $css = preg_replace( "/;\n$/", "\n", $css ); + } + + $css .= "}\n\n"; + } + + return $css; + } + + /** + * Returns medium readability, putting selectors and rule sets on new lines + * + * @param (array) selectors: Array of selectors + * @param (array) details: Array of declarations + */ + private function medium( $selectors, $details ) { + $css = ''; + foreach ( $selectors as $k => $v ) { + if ( strpos( $v, $this->token ) === 0 ) { + $css .= substr( $v, strlen( $this->token ) ); + $css .= $details[$k]; + continue; + } + else if ( $details[$k] && $details[$k] != '' ) { + $css .= "$v {\n\t" . $details[$k] . "\n}\n"; + } + } + + return $css; + } + + /** + * Returns minimum readability, breaking after every selector and it's rule set + * + * @param (array) selectors: Array of selectors + * @param (array) details: Array of declarations + */ + private function minimum( $selectors, $details ) { + $css = ''; + foreach ( $selectors as $k => $v ) { + if ( strpos( $v, $this->token ) === 0 ) { + $css .= substr( $v, strlen( $this->token ) ); + $css .= $details[$k]; + continue; + } + else if ( $details[$k] && $details[$k] != '' ) { + $css .= "$v{" . $details[$k] . "}\n"; + } + } + + return $css; + } + + /** + * Returns an unreadable, but fully compressed script + * + * @param (array) selectors: Array of selectors + * @param (array) details: Array of declarations + */ + private function none( $selectors, $details ) { + $css = ''; + foreach ( $selectors as $k => $v ) { + if ( strpos( $v, $this->token ) === 0 ) { + $css .= substr( $v, strlen( $this->token ) ); + $css .= $details[$k]; + continue; + } + else if ( $details[$k] && $details[$k] != '' ) { + $css .= trim( "$v{" . $details[$k] . "}" ); + } + } + + return $css; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Format Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Individuals.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Individuals.inc new file mode 100644 index 0000000..70b369e --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Individuals.inc @@ -0,0 +1,304 @@ + 400, + "bold" => 700, + ); + + /** + * Stash a reference to the controller on each instantiation + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->Numeric = $control->Numeric; + $this->Color = $control->Color; + $this->options = &$control->Option->options; + } + + /** + * Runs special unit/directional compressions + * + * @param (string) prop: CSS Property + * @param (string) val: Value of CSS Property + */ + public function individuals( $prop, $val ) { + // Properties should always be lowercase + $prop = strtolower( $prop ); + + // Split up each definiton for color and numeric compressions + $parts = preg_split( $this->rspace, $val ); + foreach ( $parts as &$v ) { + if ( ! $v || $v == '' ) { + continue; + } + + // Remove uneeded decimals/units + if ( $this->options['format-units'] ) { + $v = $this->Numeric->numeric( $v ); + } + + // Color compression + $v = $this->Color->color( $v ); + } + $val = trim( implode( ' ', $parts ) ); + + // Special border radius handling + if ( preg_match( $this->rborderradius, $prop ) ) { + $val = $this->borderRadius( $prop, $val ); + } + // Remove uneeded side definitions if possible + else if ( $this->options['directional-compress'] && count( $parts ) > 1 && preg_match( $this->rdirectional, $prop ) ) { + $val = $this->directionals( strtolower( $val ) ); + } + + // Font-weight converter + if ( $this->options['fontweight2num'] && ( $prop == 'font-weight' || $prop == 'font' ) ) { + $val = $this->fontweight( $val ); + } + + // Special font value conversions + if ( $prop == 'font' ) { + $val = $this->font( $val ); + } + + // Special clip value compressions + if ( $prop == 'clip' ) { + $val = $this->clip( $val ); + } + + // None to 0 converter + $val = $this->none( $prop, $val ); + + // MSIE Filters + $val = $this->filter( $prop, $val ); + + // Return for list retrival + return array($prop, $val); + } + + /** + * Preps border radius for directional compression + * + * @param (string) prop: Property Declaration + * @param (string) val: Declaration Value + */ + private function borderRadius( $prop, $val ) { + if ( preg_match( $this->rslash, $val ) ) { + $parts = preg_split( $this->rslash, $val, 2 ); + // We have to redo numeric compression because the slash may hav intruded + foreach ( $parts as &$row ) { + $p = preg_split( $this->rspace, $row ); + foreach ( $p as &$v ) { + if ( ! $v || $v == '' ) { + continue; + } + + // Remove uneeded decimals/units + if ( $this->options['format-units'] ) { + $v = $this->Numeric->numeric( $v ); + } + } + $row = implode( ' ', $p ); + if ( $this->options['directional-compress'] ) { + $row = $this->directionals( strtolower( $row ) ); + } + } + $val = implode( '/', $parts ); + } + else if ( $this->options['directional-compress'] && preg_match( $this->rradiusfull, $prop ) ) { + $val = $this->directionals( strtolower( $val ) ); + } + + return $val; + } + + /** + * Finds directional compression on methods like margin/padding + * + * @param (string) val: Value of CSS Property + */ + private function directionals( $val ) { + // Split up each definiton + $direction = preg_split( $this->rspace, $val ); + + // 4 Direction reduction + $count = count( $direction ); + if ( $count == 4 ) { + // All 4 sides are the same, combine into 1 definition + if ( $direction[0] == $direction[1] && $direction[2] == $direction[3] && $direction[0] == $direction[3] ) { + $direction = array($direction[0]); + } + // top-bottom/left-right are the same, reduce definition + else if ( $direction[0] == $direction[2] && $direction[1] == $direction[3] ) { + $direction = array($direction[0], $direction[1]); + } + // Only left-right are the same + else if ( $direction[1] == $direction[3] ) { + $direction = array($direction[0], $direction[1], $direction[2]); + } + } + // 3 Direction reduction + else if ( $count == 3 ) { + // All directions are the same + if ( $direction[0] == $direction[1] && $direction[1] == $direction[2] ) { + $direction = array($direction[0]); + } + // Only top(first) and bottom(last) are the same + else if ( $direction[0] == $direction[2] ) { + $direction = array($direction[0], $direction[1]); + } + } + // 2 Direction reduction + // Both directions are the same, combine into single definition + else if ( $count == 2 && $direction[0] == $direction[1] ) { + $direction = array($direction[0]); + } + + // Return the combined version of the directions + // Single entries will just return + return implode( ' ', $direction ); + } + + /** + * Converts font-weight names to numbers + * + * @param (string) val: font-weight prop value + */ + private function fontweight( $val ) { + if ( preg_match( $this->rspace, $val ) ) { + $parts = preg_split( $this->rspace, $val ); + foreach ( $parts as &$item ) { + $lower = strtolower( $item ); + if ( isset( $this->weights[$lower] ) && $lower != 'normal' ) { + $item = $this->weights[$lower]; + } + } + $val = implode( ' ', $parts ); + } + else if ( isset( $this->weights[strtolower( $val )] ) ) { + $val = $this->weights[strtolower( $val )]; + } + + return $val; + } + + /** + * Special font conversions + * + * @param (string) val: property value + */ + private function font( $val ) { + // Split out the font-size/line-height split and run through numerical handlers + if ( preg_match( $this->rsplitter, $val, $match, PREG_OFFSET_CAPTURE ) ) { + $size = $this->Numeric->numeric( $match[2][0] ); + $height = $this->Numeric->numeric( $match[3][0] ); + $concat = $match[1][0] . $size . '/' . $height . $match[4][0]; + $val = substr_replace( $val, $concat, $match[0][1], strlen( $match[0][0] ) ); + } + + return $val; + } + + /** + * Special clip conversions + * + * @param (string) val: property value + */ + private function clip( $val ) { + if ( preg_match( $this->rclip, $val, $match ) ) { + $positions = array(1, 3, 5, 7); + $clean = 'rect('; + foreach ( $positions as $pos ) { + if ( ! isset( $match[$pos] ) ) { + return $val; + } + + $clean .= $this->Numeric->numeric( $match[$pos] ) . ( isset( $match[$pos + 1] ) ? $match[$pos + 1] : '' ); + } + $val = $clean . ')'; + } + + return $val; + } + + /** + * Convert none vals to 0 + * + * @param (string) prop: Current Property + * @param (string) val: property value + */ + private function none( $prop, $val ) { + if ( preg_match( $this->rnoneprop, $prop ) && $val == 'none' ) { + $val = '0'; + } + + return $val; + } + + /** + * MSIE Filter Conversion + * + * @param (string) prop: Current Property + * @param (string) val: property value + */ + private function filter( $prop, $val ) { + if ( preg_match( "/filter/", $prop ) ) { + $val = preg_replace( $this->rfilter, "alpha(opacity=$1)", $val ); + } + + return $val; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Individuals Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Numeric.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Numeric.inc new file mode 100644 index 0000000..ac2b0f7 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Numeric.inc @@ -0,0 +1,100 @@ +Control = $control; + $this->options = &$control->Option->options; + } + + /** + * Runs all numeric operations + * + * @param (string) str: Unit string + */ + public function numeric( $str ) { + $str = $this->decimal( $str ); + $str = $this->zeroes( $str ); + $str = $this->units( $str ); + return $str; + } + + /** + * Remove's unecessary decimal, ie 13.0px => 13px + * + * @param (string) str: Unit string + */ + private function decimal( $str ) { + if ( preg_match( $this->rdecimal, $str, $match ) ) { + $str = ( $match[1] == '-' ? '-' : '' ) . floatval( $match[2] ) . $match[3]; + } + + return $str; + } + + /** + * Removes suffix from 0 unit, ie 0px; => 0; + * + * @param (string) str: Unit string + */ + private function units( $str ) { + if ( preg_match( $this->runit, $str, $match ) ) { + $str = '0'; + } + + return $str; + } + + + /** + * Removes leading zero in decimal, ie 0.33px => .33px + * + * @param (string) str: Unit string + */ + private function zeroes( $str ) { + if ( preg_match( $this->rzero, $str, $match ) ) { + $str = ( isset( $match[1] ) && $match[1] == '-' ? '-' : '' ) . $match[2] . ( isset( $match[3] ) ? $match[3] : '' ); + } + + return $str; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Numeric Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Option.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Option.inc new file mode 100644 index 0000000..c1c8b3f --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Option.inc @@ -0,0 +1,130 @@ +Control = $control; + $this->options = CSSCompression::$defaults; + $control->mode = $this->custom; + } + + /** + * Maintainable access to the options array + * + * - Passing no arguments returns the entire options array + * - Passing a single name argument returns the value for the option + * - Passing both a name and value, sets the value to the name key, and returns the value + * - Passing an array will merge the options with the array passed, for object like extension + * + * @param (mixed) name: The key name of the option + * @param (mixed) value: Value to set the option + */ + public function option( $name = NULL, $value = NULL ) { + if ( $name === NULL ) { + return $this->options; + } + else if ( is_array( $name ) ) { + return $this->merge( $name ); + } + else if ( $value === NULL ) { + return isset( $this->options[$name] ) ? $this->options[$name] : NULL; + } + else { + // Readability doesn't signify custom settings + if ( $name != 'readability' ) { + $this->Control->mode = $this->custom; + } + + return ( $this->options[$name] = $value ); + } + } + + /** + * Reset's the default options + * + * @params none; + */ + public function reset() { + // Reset and return the new options + return $this->options = CSSCompression::$defaults; + } + + /** + * Extend like function to merge an array of preferences into + * the options array. + * + * @param (mixed) options: Array of preferences to merge into options + */ + public function merge( $options = array() ) { + $modes = CSSCompression::modes(); + if ( $options && is_array( $options ) && count( $options ) ) { + $this->Control->mode = $this->custom; + foreach ( $this->options as $key => $value ) { + if ( ! isset( $options[$key] ) ) { + continue; + } + else if ( strtolower( $options[$key] ) == 'on' ) { + $this->options[$key] = true; + } + else if ( strtolower( $options[$key] ) == 'off' ) { + $this->options[$key] = false; + } + else { + $this->options[$key] = intval( $options[$key] ); + } + } + } + else if ( $options && is_string( $options ) && array_key_exists( $options, $modes ) ) { + $this->Control->mode = $options; + + // Default all to true, the mode has to force false + foreach ( $this->options as $key => $value ) { + if ( $key != 'readability' ) { + $this->options[$key] = true; + } + } + + // Merge mode into options + foreach ( $modes[$options] as $key => $value ) { + $this->options[$key] = $value; + } + } + + return $this->options; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Option Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Organize.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Organize.inc new file mode 100644 index 0000000..aea80a5 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Organize.inc @@ -0,0 +1,146 @@ +Control = $control; + $this->options = &$control->Option->options; + } + + /** + * Look to see if we can combine selectors to reduce the number + * of definitions. + * + * @param (array) selectors: Array of selectors, map directly to details + * @param (array) details: Array of rule sets, map directly to selectors + */ + public function organize( &$selectors = array(), &$details = array() ) { + // Combining defns based on similar selectors + list( $selectors, $details ) = $this->reduceSelectors( $selectors, $details ); + + // Combining defns based on similar details + list( $selectors, $details ) = $this->reduceDetails( $selectors, $details ); + + // Return in package form + return array($selectors, $details); + } + + /** + * Combines multiply defined selectors by merging the rule sets, + * latter declarations overide declaratins at top of file + * + * @param (array) selectors: Array of selectors broken down by setup + * @param (array) details: Array of rule sets broken down by setup + */ + private function reduceSelectors( $selectors, $details ) { + $keys = array_keys( $selectors ); + $max = array_pop( $keys ) + 1; + + for ( $i = 0; $i < $max; $i++ ) { + if ( ! isset( $selectors[$i] ) ) { + continue; + } + + for ( $k = $i + 1; $k < $max; $k++ ) { + if ( ! isset( $selectors[$k] ) ) { + continue; + } + + if ( $selectors[$i] == $selectors[$k] ) { + // Prevent noticies + if ( ! isset( $details[$i] ) ) { + $details[$i] = ''; + } + if ( ! isset( $details[$k] ) ) { + $details[$k] = ''; + } + + // We kill the last semicolon before organization, so account for that. + if ( $details[$i] != '' && $details[$k] != '' && ! preg_match( $this->rlastsemi, $details[$i] ) ) { + $details[$i] .= ';' . $details[$k]; + } + else { + $details[$i] .= $details[$k]; + } + + // Remove the second part + unset( $selectors[$k], $details[$k] ); + } + } + } + + return array($selectors, $details); + } + + /** + * Combines multiply defined rule sets by merging the selectors + * in comma seperated format + * + * @param (array) selectors: Array of selectors broken down by setup + * @param (array) details: Array of rule sets broken down by setup + */ + private function reduceDetails( $selectors, $details ) { + $keys = array_keys( $selectors ); + $max = array_pop( $keys ) + 1; + for ( $i = 0; $i < $max; $i++ ) { + if ( ! isset( $selectors[$i] ) ) { + continue; + } + + $arr = preg_split( $this->rsemicolon, isset( $details[$i] ) ? $details[$i] : '' ); + for ( $k = $i + 1; $k < $max; $k++ ) { + if ( ! isset( $selectors[$k] ) ) { + continue; + } + + $match = preg_split( $this->rsemicolon, isset( $details[$k] ) ? $details[$k] : '' ); + $x = array_diff( $arr, $match ); + $y = array_diff( $match, $arr ); + + if ( count( $x ) < 1 && count( $y ) < 1 ) { + $selectors[$i] .= ',' . $selectors[$k]; + unset( $details[$k], $selectors[$k] ); + } + } + } + + return array($selectors, $details); + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Organize Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Selectors.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Selectors.inc new file mode 100644 index 0000000..461f7df --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Selectors.inc @@ -0,0 +1,242 @@ +~\[\+\*\. ]/"; + private $rquote = "/(?Control = $control; + $this->token = CSSCompression::TOKEN; + $this->ridattr = "/\[id=$this->token(.*?)$this->token\]/"; + $this->rclassattr = "/\[class=$this->token(.*?)$this->token\]/"; + $this->options = &$control->Option->options; + } + + /** + * Selector specific optimizations + * + * @param (array) selectors: Array of selectors + */ + public function selectors( &$selectors = array() ) { + foreach ( $selectors as &$selector ) { + // Auto ignore sections + if ( strpos( $selector, $this->token ) === 0 ) { + continue; + } + + // Smart casing and token injection + $selector = $this->parse( $selector ); + + // Converting attr to shorthanded selectors + if ( $this->options['attr2selector'] ) { + // Use id hash instead of id attr + $selector = $this->idAttribute( $selector ); + + // Use class notation instead of class attr + $selector = $this->classAttribute( $selector ); + } + + // Remove everything before final id in a selector + if ( $this->options['strict-id'] ) { + $selector = $this->strictid( $selector ); + } + + // Get rid of possible repeated selectors + $selector = $this->repeats( $selector ); + + // Add space after pseudo selectors (so ie6 doesn't complain) + if ( $this->options['pseudo-space'] ) { + $selector = $this->pseudoSpace( $selector ); + } + } + + return $selectors; + } + + /** + * Converts selectors like BODY => body, DIV => div + * and injects tokens wrappers for attribute values + * + * @param (string) selector: CSS Selector + */ + private function parse( $selector ) { + $clean = ''; + $substr = ''; + $pos = 0; + + while ( preg_match( $this->rmark, $selector, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $substr = substr( $selector, $pos, $match[0][1] + 1 - $pos ); + $clean .= $this->options['lowercase-selectors'] ? strtolower( $substr ) : $substr; + $pos = $match[0][1] + strlen( $match[1][0] ); + + // Class or id match + if ( $match[1][0] == '#' || $match[1][0] == '.' ) { + if ( preg_match( $this->ridclassend, $selector, $m, PREG_OFFSET_CAPTURE, $pos ) ) { + $clean .= substr( $selector, $pos, $m[0][1] - $pos ); + $pos = $m[0][1]; + } + else { + $clean .= substr( $selector, $pos ); + $pos = strlen( $selector ); + break; + } + } + // Start of a string + else if ( preg_match( $this->rquote, $selector, $m, PREG_OFFSET_CAPTURE, $pos ) ) { + if ( $selector[$pos] == "\"" || $selector[$pos] == "'" ) { + $pos++; + } + $clean .= $this->token . substr( $selector, $pos, $m[0][1] - $pos ) . $this->token . ']'; + $pos = $m[0][1] + strlen( $m[0][0] ); + } + // No break points left + else { + $clean .= substr( $selector, $pos ); + $pos = strlen( $selector ); + break; + } + } + + return $clean . ( $this->options['lowercase-selectors'] ? strtolower( substr( $selector, $pos ) ) : substr( $selector, $pos ) ); + } + + /** + * Convert [id=blah] attribute selectors into id form selector (#blah) + * + * @param (string) selector: CSS Selector + */ + private function idAttribute( $selector ) { + $pos = 0; + while ( preg_match( $this->ridattr, $selector, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + // Don't convert if space found (not valid hash selector) + if ( strpos( $match[1][0], ' ' ) !== false ) { + $pos = $match[0][1] + strlen( $match[0][0] ); + continue; + } + + $selector = substr_replace( $selector, '#' . $match[1][0], $match[0][1], strlen( $match[0][0] ) ); + $pos = $match[0][1] + strlen( $match[1][0] ) + 1; + } + + return $selector; + } + + /** + * Convert [class=blah] attribute selectors into class form selector (.blah) + * + * @param (string) selector: CSS Selector + */ + private function classAttribute( $selector ) { + $pos = 0; + while ( preg_match( $this->rclassattr, $selector, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + // Don't convert if prescense of dot separator found + if ( strpos( $match[1][0], '.' ) !== false ) { + $pos = $match[0][1] + strlen( $match[0][0] ); + continue; + } + + $replace = '.' . preg_replace( $this->rescapedspace, ".", $match[1][0] ); + $selector = substr_replace( $selector, $replace, $match[0][1], strlen( $match[0][0] ) ); + $pos = $match[0][1] + strlen( $match[1][0] ) + 1; + } + + return $selector; + } + + /** + * Promotes nested id's to the front of the selector + * + * @param (string) selector: CSS Selector + */ + private function strictid( $selector ) { + $parts = preg_split( $this->rcomma, $selector ); + foreach ( $parts as &$s ) { + if ( preg_match( $this->rid, $s ) ) { + $p = preg_split( $this->rid, $s ); + $s = '#' . array_pop( $p ); + } + } + + return implode( ',', $parts ); + } + + /** + * Removes repeated selectors that have been comma separated + * + * @param (string) selector: CSS Selector + */ + private function repeats( $selector ) { + $parts = preg_split( $this->rcomma, $selector ); + $parts = array_flip( $parts ); + $parts = array_flip( $parts ); + return implode( ',', $parts ); + } + + /** + * Adds space after pseudo selector for ie6 like a:first-child{ => a:first-child { + * + * @param (string) selector: CSS Selector + */ + private function pseudoSpace( $selector ) { + return preg_replace( $this->rpseudo, ":first-$1 $2", $selector ); + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + if ( $method == 'selectors' ) { + return $this->selectors( $args[0] ); + } + else { + return call_user_func_array( array($this, $method), $args ); + } + } + else { + throw new CSSCompression_Exception( "Unknown method in Selectors Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Setup.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Setup.inc new file mode 100644 index 0000000..aaffb6b --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Setup.inc @@ -0,0 +1,289 @@ + array( + "/(? array( + "\n{\n", + "\n}\n", + "\n@", + "$1\n", + ), + ); + + /** + * Stash a reference to the controller on each instantiation + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->Individuals = $control->Individuals; + $this->token = CSSCompression::TOKEN; + $this->options = &$control->Option->options; + $this->stats = &$control->stats; + } + + /** + * Setup selector and details arrays for compression methods + * + * @param (string) css: Trimed stylesheet + */ + public function setup( $css ) { + // Seperate the element from the elements details + $css = explode( "\n", preg_replace( $this->rsetup['patterns'], $this->rsetup['replacements'], $css ) ); + $newline = $this->options['readability'] > CSSCompression::READ_NONE ? "\n" : ''; + $setup = array( + 'selectors' => array(), + 'details' => array(), + 'unknown' => array(), + 'introliner' => '', + 'namespace' => '', + 'import' => '', + 'charset' => '', + ); + + while ( count( $css ) ) { + $row = trim( array_shift( $css ) ); + + if ( $row == '' ) { + continue; + } + // Single block At-Rule set + else if ( $row[0] == '@' && $css[0] == '{' && trim( $css[1] ) != '' && $css[2] == '}' ) { + // Stash selector + array_push( $setup['selectors'], $row ); + + // Stash details (after the opening brace) + array_push( $setup['details'], $this->details( trim( $css[1] ) ) ); + + // drop the details from the stack + $css = array_slice( $css, 3 ); + } + // Single line At-Rules (import/charset/namespace) + else if ( preg_match( $this->rliner, $row, $match ) ) { + $setup[$match[1]] .= $this->liner( $row ) . $newline; + } + // Nested At-Rule declaration blocks + else if ( $row[0] == '@' && $css[0] == '{' ) { + // Stash atrule as selector + array_push( $setup['selectors'], $this->token . $row ); + + // Stash details (after the opening brace) + array_push( $setup['details'], $this->nested( $css, preg_match( $this->rmedia, $row ) ) . $newline ); + } + // Unknown single line At-Rules + else if ( $row[0] == '@' && substr( $row, -1 ) == ';' ) { + $setup['introliner'] .= $row . $newline; + } + // Declaration Block + else if ( count( $css ) >= 3 && $css[0] == '{' && $css[2] == '}' ) { + // Stash selector + array_push( $setup['selectors'], $row ); + + // Stash details (after the opening brace) + array_push( $setup['details'], $this->details( trim( $css[1] ) ) ); + + // drop the details from the stack + $css = array_slice( $css, 3 ); + } + // Last catch, store unknown artifacts as selectors with a token + // and give it an empty rule set + else { + // Still add to unknown stack, for notification + array_push( $setup['unknown'], $row ); + + // Stash unknown artifacts as selectors with a token + array_push( $setup['selectors'], $this->token . $row ); + + // Give it an empty rule set + array_push( $setup['details'], '' ); + } + } + + return $setup; + } + + /** + * Run nested elements through a separate instance of compression + * + * @param (array) css: Reference to the original css array + * @param (bool) organize: Whether or not to organize the subsection (only true for media sections) + */ + private function nested( &$css = array(), $organize = false ) { + $options = $this->options; + $left = 0; + $right = 0; + $row = ''; + $independent = ''; + $content = ''; + $spacing = ''; + $newline = $this->options['readability'] > CSSCompression::READ_NONE ? "\n" : ''; + + // Find the end of the nested section + while ( count( $css ) && ( $left < 1 || $left > $right ) ) { + $row = trim( array_shift( $css ) ); + + if ( $row == '' ) { + continue; + } + else if ( $row == '{' ) { + $left++; + } + else if ( $row == '}' ) { + $right++; + } + else if ( count( $css ) && substr( $row, 0, 1 ) != '@' && substr( $css[0], 0, 1 ) == '@' && substr( $row, -1 ) == ';' ) { + $independent .= $row; + continue; + } + + $content .= $row; + } + + // Ensure copy of instance exists + if ( ! $this->instance ) { + $this->instance = new CSSCompression(); + } + + // Fresh start + $this->instance->reset(); + + // Compress the nested section independently after removing the wrapping braces + // Also make sure to only organize media sections + if ( $options['organize'] == true && $organize == false ) { + $options['organize'] = false; + } + // Independent sections should be prepended to the next compressed section + $content = ( $independent == '' ? '' : $independent . $newline ) + . $this->instance->compress( substr( $content, 1, -1 ), $options ); + + // Formatting for anything higher then 0 readability + if ( $newline == "\n" ) { + $content = "\n\t" . str_replace( "\n", "\n\t", $content ) . "\n"; + $spacing = $this->options['readability'] > CSSCompression::READ_MIN ? ' ' : ''; + } + + // Stash the compressed nested script + return "$spacing{" . $content . "}$newline"; + } + + /** + * Converts import/namespace urls into strings + * + * @param (string) row: At-rule + */ + private function liner( $row ) { + $pos = 0; + while ( preg_match( $this->rurl, $row, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + $quote = preg_match( $this->rsinglequote, $match[1][0] ) ? '"' : "'"; + $replace = $quote . $match[1][0] . $quote; + $row = substr_replace( $row, $replace, $match[0][1], strlen( $match[0][0] ) ); + $pos = $match[0][1] + strlen( $replace ); + } + + return $row; + } + + /** + * Run individual compression techniques on each property of a selector + * + * @param (string) row: Selector properties + */ + private function details( $row ) { + $row = preg_split( $this->rsemicolon, $row ); + $parts = array(); + $details = ''; + + foreach ( $row as $line ) { + // Set loopers + $parts = preg_split( $this->rcolon, $line, 2 ); + $prop = ''; + $value = ''; + + // Property + if ( isset( $parts[0] ) && ( $parts[0] = trim( $parts[0] ) ) != '' ) { + $prop = $parts[0]; + } + + // Value + if ( isset( $parts[1] ) && ( $parts[1] = trim( $parts[1] ) ) != '' ) { + $value = preg_replace( $this->rbang, ' !', $parts[1] ); + } + + // Fail safe, remove unspecified property/values + if ( $prop == '' || $value == '' ) { + continue; + } + + // Run the tag/element through each compression + list( $prop, $value ) = $this->Individuals->individuals( $prop, $value ); + + // Add counter to before stats + $this->stats['before']['props']++; + + // Store the compressed element + $details .= "$prop:" . preg_replace( $this->rspacebang, '!', $value ) . ";"; + } + + return $details; + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Setup Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Trim.inc b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Trim.inc new file mode 100644 index 0000000..48cb098 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/css-compressor-3.x/src/lib/Trim.inc @@ -0,0 +1,210 @@ + array( + "/(?\~\+\/])(? array( + '$2', + 'url($1)', + 'url($1)', + 'url($1)', + ';', + ' ', + ), + ); + private $escaped = array( + 'search' => array( + ":", + ";", + "}", + "{", + "@", + "!", + ",", + ">", + "+", + "~", + "/", + "*", + ".", + "=", + "#", + "\r", + "\n", + "\t", + " ", + ), + 'replace' => array( + "\\:", + "\\;", + "\\}", + "\\{", + "\\@", + "\\!", + "\\,", + "\\>", + "\\+", + "\\~", + "\\/", + "\\*", + "\\.", + "\\=", + "\\#", + "\\r", + "\\n", + "\\t", + "\\ ", + ), + ); + + /** + * Stash a reference to the controller on each instantiation + * + * @param (class) control: CSSCompression Controller + */ + public function __construct( CSSCompression_Control $control ) { + $this->Control = $control; + $this->options = &$control->Option->options; + } + + /** + * Central trim handler + * + * @param (string) css: Stylesheet to trim + */ + public function trim( $css ) { + $css = $this->comments( $css ); + $css = $this->escape( $css ); + $css = $this->strip( $css ); + return $css; + } + + /** + * Does a quick run through the script to remove all comments from the sheet, + * + * @param (string) css: Stylesheet to trim + */ + private function comments( $css ) { + $pos = 0; + while ( preg_match( $this->rcmark, $css, $match, PREG_OFFSET_CAPTURE, $pos ) ) { + switch ( $match[1][0] ) { + // Start of comment block + case "/*": + if ( preg_match( $this->rendcomment, $css, $m, PREG_OFFSET_CAPTURE, $match[1][1] + 1 ) ) { + $end = $m[0][1] - $match[1][1] + strlen( $m[0][0] ); + $css = substr_replace( $css, '', $match[1][1], $end ); + $pos = $match[0][1]; + } + else { + $css = substr( $css, 0, $match[1][1] ); + break 2; + } + break; + // Start of string + case "\"": + if ( preg_match( $this->rendquote, $css, $m, PREG_OFFSET_CAPTURE, $match[1][1] + 1 ) ) { + $pos = $m[0][1] + strlen( $m[0][0] ); + } + else { + break 2; + } + break; + // Start of string + case "'": + if ( preg_match( $this->rendsinglequote, $css, $m, PREG_OFFSET_CAPTURE, $match[1][1] + 1 ) ) { + $pos = $m[0][1] + strlen( $m[0][0] ); + } + else { + break 2; + } + break; + // Should have never gotten here + default: + break 2; + } + } + + return $css; + } + + /** + * Escape out possible splitter characters within urls + * + * @param (string) css: Full stylesheet + */ + private function escape( $css ) { + foreach ( $this->rescape as $regex ) { + $start = 0; + while ( preg_match( $regex, $css, $match, PREG_OFFSET_CAPTURE, $start ) ) { + $value = $match[1][0] + . str_replace( $this->escaped['search'], $this->escaped['replace'], $match[2][0] ) + . $match[3][0]; + $css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) ); + $start = $match[0][1] + strlen( $value ) + 1; + } + } + + return $css; + } + + /** + * Runs initial formatting to setup for compression + * + * @param (string) css: CSS Contents + */ + private function strip( $css ) { + // Run replacements + return trim( preg_replace( $this->trimmings['patterns'], $this->trimmings['replacements'], $css ) ); + } + + /** + * Access to private methods for testing + * + * @param (string) method: Method to be called + * @param (array) args: Array of paramters to be passed in + */ + public function access( $method, $args ) { + if ( method_exists( $this, $method ) ) { + return call_user_func_array( array($this, $method), $args ); + } + else { + throw new CSSCompression_Exception( "Unknown method in Trim Class - " . $method ); + } + } +} diff --git a/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy.inc b/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy.inc new file mode 100644 index 0000000..6a9f264 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy.inc @@ -0,0 +1,1179 @@ +. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 + * @author Cedric Morin (cedric at yterium dot com) 2010 + */ +/** + * Defines ctype functions if required + * + * @version 1.0 + */ +require_once('class.csstidy_ctype.inc'); + +/** + * Various CSS data needed for correct optimisations etc. + * + * @version 1.3 + */ +require('data.inc'); + +/** + * Contains a class for printing CSS code + * + * @version 1.0 + */ +require('class.csstidy_print.inc'); + +/** + * Contains a class for optimising CSS code + * + * @version 1.0 + */ +require('class.csstidy_optimise.inc'); + +/** + * CSS Parser class + * + + * This class represents a CSS parser which reads CSS code and saves it in an array. + * In opposite to most other CSS parsers, it does not use regular expressions and + * thus has full CSS2 support and a higher reliability. + * Additional to that it applies some optimisations and fixes to the CSS code. + * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2006 + * @version 1.3.1 + */ +class csstidy { + + /** + * Saves the parsed CSS. This array is empty if preserve_css is on. + * @var array + * @access public + */ + var $css = array(); + /** + * Saves the parsed CSS (raw) + * @var array + * @access private + */ + var $tokens = array(); + /** + * Printer class + * @see csstidy_print + * @var object + * @access public + */ + var $print; + /** + * Optimiser class + * @see csstidy_optimise + * @var object + * @access private + */ + var $optimise; + /** + * Saves the CSS charset (@charset) + * @var string + * @access private + */ + var $charset = ''; + /** + * Saves all @import URLs + * @var array + * @access private + */ + var $import = array(); + /** + * Saves the namespace + * @var string + * @access private + */ + var $namespace = ''; + /** + * Contains the version of csstidy + * @var string + * @access private + */ + var $version = '1.3'; + /** + * Stores the settings + * @var array + * @access private + */ + var $settings = array(); + /** + * Saves the parser-status. + * + * Possible values: + * - is = in selector + * - ip = in property + * - iv = in value + * - instr = in string (started at " or ' or ( ) + * - ic = in comment (ignore everything) + * - at = in @-block + * + * @var string + * @access private + */ + var $status = 'is'; + /** + * Saves the current at rule (@media) + * @var string + * @access private + */ + var $at = ''; + /** + * Saves the current selector + * @var string + * @access private + */ + var $selector = ''; + /** + * Saves the current property + * @var string + * @access private + */ + var $property = ''; + /** + * Saves the position of , in selectors + * @var array + * @access private + */ + var $sel_separate = array(); + /** + * Saves the current value + * @var string + * @access private + */ + var $value = ''; + /** + * Saves the current sub-value + * + * Example for a subvalue: + * background:url(foo.png) red no-repeat; + * "url(foo.png)", "red", and "no-repeat" are subvalues, + * seperated by whitespace + * @var string + * @access private + */ + var $sub_value = ''; + /** + * Array which saves all subvalues for a property. + * @var array + * @see sub_value + * @access private + */ + var $sub_value_arr = array(); + /** + * Saves the char which opened the last string + * @var string + * @access private + */ + var $str_char = ''; + var $cur_string = ''; + /** + * Status from which the parser switched to ic or instr + * @var string + * @access private + */ + var $from = ''; + /** + * Variable needed to manage string-in-strings, for example url("foo.png") + * @var string + * @access private + */ + var $str_in_str = false; + /** + * =true if in invalid at-rule + * @var bool + * @access private + */ + var $invalid_at = false; + /** + * =true if something has been added to the current selector + * @var bool + * @access private + */ + var $added = false; + /** + * Array which saves the message log + * @var array + * @access private + */ + var $log = array(); + /** + * Saves the line number + * @var integer + * @access private + */ + var $line = 1; + /** + * Marks if we need to leave quotes for a string + * @var string + * @access private + */ + var $quoted_string = false; + + /** + * List of tokens + * @var string + */ + var $tokens_list = ""; + /** + * Loads standard template and sets default settings + * @access private + * @version 1.3 + */ + function csstidy() { + $this->settings['remove_bslash'] = true; + $this->settings['compress_colors'] = true; + $this->settings['compress_font-weight'] = true; + $this->settings['lowercase_s'] = false; + /* + 1 common shorthands optimization + 2 + font property optimization + 3 + background property optimization + */ + $this->settings['optimise_shorthands'] = 1; + $this->settings['remove_last_;'] = true; + /* rewrite all properties with low case, better for later gzip OK, safe*/ + $this->settings['case_properties'] = 1; + /* sort properties in alpabetic order, better for later gzip + * but can cause trouble in case of overiding same propertie or using hack + */ + $this->settings['sort_properties'] = false; + /* + 1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{} + 2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{} + preserve order by default cause it can break functionnality + */ + $this->settings['sort_selectors'] = 0; + /* is dangeroues to be used: CSS is broken sometimes */ + $this->settings['merge_selectors'] = 0; + /* preserve or not browser hacks */ + $this->settings['discard_invalid_selectors'] = false; + $this->settings['discard_invalid_properties'] = false; + $this->settings['css_level'] = 'CSS2.1'; + $this->settings['preserve_css'] = false; + $this->settings['timestamp'] = false; + $this->settings['template'] = ''; // say that propertie exist + $this->set_cfg('template', 'default'); // call load_template + $this->optimise = new csstidy_optimise($this); + + $this->tokens_list = & $GLOBALS['csstidy']['tokens']; + } + + /** + * Get the value of a setting. + * @param string $setting + * @access public + * @return mixed + * @version 1.0 + */ + function get_cfg($setting) { + if (isset($this->settings[$setting])) { + return $this->settings[$setting]; + } + return false; + } + + /** + * Load a template + * @param string $template used by set_cfg to load a template via a configuration setting + * @access private + * @version 1.4 + */ + function _load_template($template) { + switch ($template) { + case 'default': + $this->load_template('default'); + break; + + case 'highest': + $this->load_template('highest_compression'); + break; + + case 'high': + $this->load_template('high_compression'); + break; + + case 'low': + $this->load_template('low_compression'); + break; + + default: + $this->load_template($template); + break; + } + } + + /** + * Set the value of a setting. + * @param string $setting + * @param mixed $value + * @access public + * @return bool + * @version 1.0 + */ + function set_cfg($setting, $value = null) { + if (is_array($setting) && $value === null) { + foreach ($setting as $setprop => $setval) { + $this->settings[$setprop] = $setval; + } + if (array_key_exists('template', $setting)) { + $this->_load_template($this->settings['template']); + } + return true; + } + else if (isset($this->settings[$setting]) && $value !== '') { + $this->settings[$setting] = $value; + if ($setting === 'template') { + $this->_load_template($this->settings['template']); + } + return true; + } + return false; + } + + /** + * Adds a token to $this->tokens + * @param mixed $type + * @param string $data + * @param bool $do add a token even if preserve_css is off + * @access private + * @version 1.0 + */ + function _add_token($type, $data, $do = false) { + if ($this->get_cfg('preserve_css') || $do) { + $this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data)); + } + } + + /** + * Add a message to the message log + * @param string $message + * @param string $type + * @param integer $line + * @access private + * @version 1.0 + */ + function log($message, $type, $line = -1) { + if ($line === -1) { + $line = $this->line; + } + $line = intval($line); + $add = array( + 'm' => $message, + 't' => $type, + ); + if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) { + $this->log[$line][] = $add; + } + } + + /** + * Parse unicode notations and find a replacement character + * @param string $string + * @param integer $i + * @access private + * @return string + * @version 1.2 + */ + function _unicode(&$string, &$i) { + ++$i; + $add = ''; + $replaced = false; + + while ($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6) { + $add .= $string{$i}; + + if (ctype_space($string{$i})) { + break; + } + $i++; + } + + if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) { + $this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information'); + $add = chr(hexdec($add)); + $replaced = true; + } + else { + $add = trim('\\' . $add); + } + + if (@ctype_xdigit($string{$i + 1}) && ctype_space($string{$i}) + && !$replaced || !ctype_space($string{$i})) { + $i--; + } + + if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string{$i + 1}) !== false) { + return $add; + } + + if ($add === '\\') { + $this->log('Removed unnecessary backslash', 'Information'); + } + return ''; + } + + /** + * Write formatted output to a file + * @param string $filename + * @param string $doctype when printing formatted, is a shorthand for the document type + * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet + * @param string $title when printing formatted, is the title to be added in the head of the document + * @param string $lang when printing formatted, gives a two-letter language code to be added to the output + * @access public + * @version 1.4 + */ + function write_page($filename, $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en') { + $this->write($filename, true); + } + + /** + * Write plain output to a file + * @param string $filename + * @param bool $formatted whether to print formatted or not + * @param string $doctype when printing formatted, is a shorthand for the document type + * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet + * @param string $title when printing formatted, is the title to be added in the head of the document + * @param string $lang when printing formatted, gives a two-letter language code to be added to the output + * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates) + * @access public + * @version 1.4 + */ + function write($filename, $formatted = false, $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en', $pre_code = true) { + $filename .= ( $formatted) ? '.xhtml' : '.css'; + + if (!is_dir('temp')) { + $madedir = mkdir('temp'); + if (!$madedir) { + print 'Could not make directory "temp" in ' . dirname(__FILE__); + exit; + } + } + $handle = fopen('temp/' . $filename, 'w'); + if ($handle) { + if (!$formatted) { + fwrite($handle, $this->print->plain()); + } + else { + fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code)); + } + } + fclose($handle); + } + + /** + * Loads a new template + * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default" + * @param bool $from_file uses $content as filename if true + * @access public + * @version 1.1 + * @see http://csstidy.sourceforge.net/templates.php + */ + function load_template($content, $from_file = true) { + $predefined_templates = & $GLOBALS['csstidy']['predefined_templates']; + if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') { + $this->template = $predefined_templates[$content]; + return; + } + + + if ($from_file) { + $content = strip_tags(file_get_contents($content), ''); + } + $content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n) + $template = explode('|', $content); + + for ($i = 0; $i < count($template); $i++) { + $this->template[$i] = $template[$i]; + } + } + + /** + * Starts parsing from URL + * @param string $url + * @access public + * @version 1.0 + */ + function parse_from_url($url) { + return $this->parse(@file_get_contents($url)); + } + + /** + * Checks if there is a token at the current position + * @param string $string + * @param integer $i + * @access public + * @version 1.11 + */ + function is_token(&$string, $i) { + return (strpos($this->tokens_list, $string{$i}) !== false && !csstidy::escaped($string, $i)); + } + + /** + * Parses CSS in $string. The code is saved as array in $this->css + * @param string $string the CSS code + * @access public + * @return bool + * @version 1.1 + */ + function parse($string) { + // Temporarily set locale to en_US in order to handle floats properly + $old = @setlocale(LC_ALL, 0); + @setlocale(LC_ALL, 'C'); + + // PHP bug? Settings need to be refreshed in PHP4 + $this->print = new csstidy_print($this); + $this->optimise = new csstidy_optimise($this); + + $all_properties = & $GLOBALS['csstidy']['all_properties']; + $at_rules = & $GLOBALS['csstidy']['at_rules']; + + $this->css = array(); + $this->print->input_css = $string; + $string = str_replace("\r\n", "\n", $string) . ' '; + $cur_comment = ''; + + for ($i = 0, $size = strlen($string); $i < $size; $i++) { + if ($string{$i} === "\n" || $string{$i} === "\r") { + ++$this->line; + } + + switch ($this->status) { + /* Case in at-block */ + case 'at': + if (csstidy::is_token($string, $i)) { + if ($string{$i} === '/' && @$string{$i + 1} === '*') { + $this->status = 'ic'; + ++$i; + $this->from = 'at'; + } + elseif ($string{$i} === '{') { + $this->status = 'is'; + $this->at = $this->css_new_media_section($this->at); + $this->_add_token(AT_START, $this->at); + } + elseif ($string{$i} === ',') { + $this->at = trim($this->at) . ','; + } + elseif ($string{$i} === '\\') { + $this->at .= $this->_unicode($string, $i); + } + // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:0) + elseif (in_array($string{$i}, array('(', ')', ':'))) { + $this->at .= $string{$i}; + } + } + else { + $lastpos = strlen($this->at) - 1; + if (!( (ctype_space($this->at{$lastpos}) || csstidy::is_token($this->at, $lastpos) && $this->at{$lastpos} === ',') && ctype_space($string{$i}))) { + $this->at .= $string{$i}; + } + } + break; + + /* Case in-selector */ + case 'is': + if (csstidy::is_token($string, $i)) { + if ($string{$i} === '/' && @$string{$i + 1} === '*' && trim($this->selector) == '') { + $this->status = 'ic'; + ++$i; + $this->from = 'is'; + } + elseif ($string{$i} === '@' && trim($this->selector) == '') { + // Check for at-rule + $this->invalid_at = true; + foreach ($at_rules as $name => $type) { + if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) { + ($type === 'at') ? $this->at = '@' . $name : $this->selector = '@' . $name; + $this->status = $type; + $i += strlen($name); + $this->invalid_at = false; + } + } + + if ($this->invalid_at) { + $this->selector = '@'; + $invalid_at_name = ''; + for ($j = $i + 1; $j < $size; ++$j) { + if (!ctype_alpha($string{$j})) { + break; + } + $invalid_at_name .= $string{$j}; + } + $this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning'); + } + } + elseif (($string{$i} === '"' || $string{$i} === "'")) { + $this->cur_string = $string{$i}; + $this->status = 'instr'; + $this->str_char = $string{$i}; + $this->from = 'is'; + /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */ + $this->quoted_string = ($string{$i - 1} == '=' ); + } + elseif ($this->invalid_at && $string{$i} === ';') { + $this->invalid_at = false; + $this->status = 'is'; + } + elseif ($string{$i} === '{') { + $this->status = 'ip'; + if ($this->at == '') { + $this->at = $this->css_new_media_section(DEFAULT_AT); + } + $this->selector = $this->css_new_selector($this->at, $this->selector); + $this->_add_token(SEL_START, $this->selector); + $this->added = false; + } + elseif ($string{$i} === '}') { + $this->_add_token(AT_END, $this->at); + $this->at = ''; + $this->selector = ''; + $this->sel_separate = array(); + } + elseif ($string{$i} === ',') { + $this->selector = trim($this->selector) . ','; + $this->sel_separate[] = strlen($this->selector); + } + elseif ($string{$i} === '\\') { + $this->selector .= $this->_unicode($string, $i); + } + elseif ($string{$i} === '*' && @in_array($string{$i + 1}, array('.', '#', '[', ':'))) { + // remove unnecessary universal selector, FS#147 + } + else { + $this->selector .= $string{$i}; + } + } + else { + $lastpos = strlen($this->selector) - 1; + if ($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || csstidy::is_token($this->selector, $lastpos) && $this->selector{$lastpos} === ',') && ctype_space($string{$i}))) { + $this->selector .= $string{$i}; + } + } + break; + + /* Case in-property */ + case 'ip': + if (csstidy::is_token($string, $i)) { + if (($string{$i} === ':' || $string{$i} === '=') && $this->property != '') { + $this->status = 'iv'; + if (!$this->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) { + $this->property = $this->css_new_property($this->at, $this->selector, $this->property); + $this->_add_token(PROPERTY, $this->property); + } + } + elseif ($string{$i} === '/' && @$string{$i + 1} === '*' && $this->property == '') { + $this->status = 'ic'; + ++$i; + $this->from = 'ip'; + } + elseif ($string{$i} === '}') { + $this->explode_selectors(); + $this->status = 'is'; + $this->invalid_at = false; + $this->_add_token(SEL_END, $this->selector); + $this->selector = ''; + $this->property = ''; + } + elseif ($string{$i} === ';') { + $this->property = ''; + } + elseif ($string{$i} === '\\') { + $this->property .= $this->_unicode($string, $i); + } + // else this is dumb IE a hack, keep it + elseif ($this->property == '' AND !ctype_space($string{$i})) { + $this->property .= $string{$i}; + } + } + elseif (!ctype_space($string{$i})) { + $this->property .= $string{$i}; + } + break; + + /* Case in-value */ + case 'iv': + $pn = (($string{$i} === "\n" || $string{$i} === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1); + if (csstidy::is_token($string, $i) || $pn) { + if ($string{$i} === '/' && @$string{$i + 1} === '*') { + $this->status = 'ic'; + ++$i; + $this->from = 'iv'; + } + elseif (($string{$i} === '"' || $string{$i} === "'" || $string{$i} === '(')) { + $this->cur_string = $string{$i}; + $this->str_char = ($string{$i} === '(') ? ')' : $string{$i}; + $this->status = 'instr'; + $this->from = 'iv'; + } + elseif ($string{$i} === ',') { + $this->sub_value = trim($this->sub_value) . ','; + } + elseif ($string{$i} === '\\') { + $this->sub_value .= $this->_unicode($string, $i); + } + elseif ($string{$i} === ';' || $pn) { + if ($this->selector{0} === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') { + /* Add quotes to charset, import, namespace */ + $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"'; + + $this->status = 'is'; + + switch ($this->selector) { + case '@charset': + $this->charset = $this->sub_value_arr[0]; + break; + case '@namespace': + $this->namespace = implode(' ', $this->sub_value_arr); + break; + case '@import': + $this->import[] = implode(' ', $this->sub_value_arr); + break; + } + + $this->sub_value_arr = array(); + $this->sub_value = ''; + $this->selector = ''; + $this->sel_separate = array(); + } + else { + $this->status = 'ip'; + } + } + elseif ($string{$i} !== '}') { + $this->sub_value .= $string{$i}; + } + if (($string{$i} === '}' || $string{$i} === ';' || $pn) && !empty($this->selector)) { + if ($this->at == '') { + $this->at = $this->css_new_media_section(DEFAULT_AT); + } + + // case settings + if ($this->get_cfg('lowercase_s')) { + $this->selector = strtolower($this->selector); + } + $this->property = strtolower($this->property); + + $this->optimise->subvalue(); + if ($this->sub_value != '') { + /* original, disabled for fix below + if (substr($this->sub_value, 0, 6) == 'format') { + $this->sub_value = str_replace(array('format(', ')'), array('format("', '")'), $this->sub_value); + }//*/ + $this->sub_value_arr[] = $this->sub_value; + // [FIX] @font-face with multiple fonts and CSSTidy + // (http://www.pixelastic.com/blog/86:csstidy-and-the-woff-fonts) + foreach ($this->sub_value_arr as $sub_value) { + if (substr($sub_value, 0, 6) == 'format') { + $sub_value = str_replace(array('format(', ')'), array('format("', '")'), $sub_value); + } + } + $this->sub_value = ''; + } + + $this->value = array_shift($this->sub_value_arr); + while (count($this->sub_value_arr)) { + $this->value .= (substr($this->value, -1, 1) == ',' ? '' : ' ') . array_shift($this->sub_value_arr); + } + + $this->optimise->value(); + + $valid = csstidy::property_is_valid($this->property); + if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) { + $this->css_add_property($this->at, $this->selector, $this->property, $this->value); + $this->_add_token(VALUE, $this->value); + $this->optimise->shorthands(); + } + if (!$valid) { + if ($this->get_cfg('discard_invalid_properties')) { + $this->log('Removed invalid property: ' . $this->property, 'Warning'); + } + else { + $this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning'); + } + } + + $this->property = ''; + $this->sub_value_arr = array(); + $this->value = ''; + } + if ($string{$i} === '}') { + $this->explode_selectors(); + $this->_add_token(SEL_END, $this->selector); + $this->status = 'is'; + $this->invalid_at = false; + $this->selector = ''; + } + } + elseif (!$pn) { + $this->sub_value .= $string{$i}; + + if (ctype_space($string{$i})) { + $this->optimise->subvalue(); + if ($this->sub_value != '') { + $this->sub_value_arr[] = $this->sub_value; + $this->sub_value = ''; + } + } + } + break; + + /* Case in string */ + case 'instr': + if ($this->str_char === ')' && ($string{$i} === '"' || $string{$i} === '\'') && !$this->str_in_str && !csstidy::escaped($string, $i)) { + $this->str_in_str = true; + } + elseif ($this->str_char === ')' && ($string{$i} === '"' || $string{$i} === '\'') && $this->str_in_str && !csstidy::escaped($string, $i)) { + $this->str_in_str = false; + } + $temp_add = $string{$i}; // ...and no not-escaped backslash at the previous position + if (($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !csstidy::escaped($string, $i - 1))) { + $temp_add = "\\A "; + $this->log('Fixed incorrect newline in string', 'Warning'); + } + // this optimisation remove space in css3 properties (see vendor-prefixed/webkit-gradient.csst) + #if (!($this->str_char === ')' && in_array($string{$i}, $GLOBALS['csstidy']['whitespace']) && !$this->str_in_str)) { + $this->cur_string .= $temp_add; + #} + if ($string{$i} == $this->str_char && !csstidy::escaped($string, $i) && !$this->str_in_str) { + $this->status = $this->from; + if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $this->cur_string) && $this->property !== 'content') { + if (!$this->quoted_string) { + if ($this->str_char === '"' || $this->str_char === '\'') { + // Temporarily disable this optimization to avoid problems with @charset rule, quote properties, and some attribute selectors... + // Attribute selectors fixed, added quotes to @chartset, no problems with properties detected. Enabled + $this->cur_string = substr($this->cur_string, 1, -1); + } + else if (strlen($this->cur_string) > 3 && ($this->cur_string[1] === '"' || $this->cur_string[1] === '\'')) { + $this->cur_string = $this->cur_string[0] . substr($this->cur_string, 2, -2) . substr($this->cur_string, -1); + } + } + else { + $this->quoted_string = false; + } + } + if ($this->from === 'iv') { + if (!$this->quoted_string) { + if (strpos($this->cur_string, ',') !== false) { // we can on only remove space next to ',' + $this->cur_string = implode(',', array_map('trim', explode(',', $this->cur_string))); + } + // and multiple spaces (too expensive) + if (strpos($this->cur_string, ' ') !== false) { + $this->cur_string = preg_replace(",\s+,", " ", $this->cur_string); + } + } + $this->sub_value .= $this->cur_string; + } + elseif ($this->from === 'is') { + $this->selector .= $this->cur_string; + } + } + break; + + /* Case in-comment */ + case 'ic': + if ($string{$i} === '*' && $string{$i + 1} === '/') { + $this->status = $this->from; + $i++; + $this->_add_token(COMMENT, $cur_comment); + $cur_comment = ''; + } + else { + $cur_comment .= $string{$i}; + } + break; + } + } + + $this->optimise->postparse(); + + $this->print->_reset(); + + @setlocale(LC_ALL, $old); // Set locale back to original setting + + return; + (empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace)); + } + + /** + * Explodes selectors + * @access private + * @version 1.0 + */ + function explode_selectors() { + // Explode multiple selectors + if ($this->get_cfg('merge_selectors') === 1) { + $new_sels = array(); + $lastpos = 0; + $this->sel_separate[] = strlen($this->selector); + foreach ($this->sel_separate as $num => $pos) { + if ($num == count($this->sel_separate) - 1) { + $pos += 1; + } + + $new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1); + $lastpos = $pos; + } + + if (count($new_sels) > 1) { + foreach ($new_sels as $selector) { + if (isset($this->css[$this->at][$this->selector])) { + $this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]); + } + } + unset($this->css[$this->at][$this->selector]); + } + } + $this->sel_separate = array(); + } + + /** + * Checks if a character is escaped (and returns true if it is) + * @param string $string + * @param integer $pos + * @access public + * @return bool + * @version 1.02 + */ + static function escaped(&$string, $pos) { + return; + (@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1)); + } + + /** + * Adds a property with value to the existing CSS code + * @param string $media + * @param string $selector + * @param string $property + * @param string $new_val + * @access private + * @version 1.2 + */ + function css_add_property($media, $selector, $property, $new_val) { + if ($this->get_cfg('preserve_css') || trim($new_val) == '') { + return; + } + + $this->added = true; + if (isset($this->css[$media][$selector][$property])) { + if ((csstidy::is_important($this->css[$media][$selector][$property]) && csstidy::is_important($new_val)) || !csstidy::is_important($this->css[$media][$selector][$property])) { + $this->css[$media][$selector][$property] = trim($new_val); + } + } + else { + $this->css[$media][$selector][$property] = trim($new_val); + } + } + + /** + * Start a new media section. + * Check if the media is not already known, + * else rename it with extra spaces + * to avoid merging + * + * @param string $media + * @return string + */ + function css_new_media_section($media) { + if ($this->get_cfg('preserve_css')) { + return $media; + } + + // if the last @media is the same as this + // keep it + if (!$this->css OR !is_array($this->css) OR empty($this->css)) { + return $media; + } + end($this->css); + list($at, ) = each($this->css); + if ($at == $media) { + return $media; + } + while (isset($this->css[$media])) { + if (is_numeric($media)) { + $media++; + } + else { + $media .= " "; + } + } + return $media; + } + + /** + * Start a new selector. + * If already referenced in this media section, + * rename it with extra space to avoid merging + * except if merging is required, + * or last selector is the same (merge siblings) + * + * never merge @font-face + * + * @param string $media + * @param string $selector + * @return string + */ + function css_new_selector($media, $selector) { + if ($this->get_cfg('preserve_css')) { + return $selector; + } + $selector = trim($selector); + if (strncmp($selector, "@font-face", 10) != 0) { + if ($this->settings['merge_selectors'] != false) { + return $selector; + } + + if (!$this->css OR !isset($this->css[$media]) OR !$this->css[$media]) { + return $selector; + } + + // if last is the same, keep it + end($this->css[$media]); + list($sel, ) = each($this->css[$media]); + if ($sel == $selector) { + return $selector; + } + } + + while (isset($this->css[$media][$selector])) { + $selector .= " "; + } + return $selector; + } + + /** + * Start a new propertie. + * If already references in this selector, + * rename it with extra space to avoid override + * + * @param string $media + * @param string $selector + * @param string $property + * @return string + */ + function css_new_property($media, $selector, $property) { + if ($this->get_cfg('preserve_css')) { + return $property; + } + if (!$this->css OR !isset($this->css[$media][$selector]) OR !$this->css[$media][$selector]) { + return $property; + } + + while (isset($this->css[$media][$selector][$property])) { + $property .= " "; + } + + return $property; + } + + /** + * Adds CSS to an existing media/selector + * @param string $media + * @param string $selector + * @param array $css_add + * @access private + * @version 1.1 + */ + function merge_css_blocks($media, $selector, $css_add) { + foreach ($css_add as $property => $value) { + $this->css_add_property($media, $selector, $property, $value, false); + } + } + + /** + * Checks if $value is !important. + * @param string $value + * @return bool + * @access public + * @version 1.0 + */ + static function is_important(&$value) { + return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'], '', $value), -10, 10), '!important')); + } + + /** + * Returns a value without !important + * @param string $value + * @return string + * @access public + * @version 1.0 + */ + static function gvw_important($value) { + if (csstidy::is_important($value)) { + $value = trim($value); + $value = substr($value, 0, -9); + $value = trim($value); + $value = substr($value, 0, -1); + $value = trim($value); + return $value; + } + return $value; + } + + /** + * Checks if the next word in a string from pos is a CSS property + * @param string $istring + * @param integer $pos + * @return bool + * @access private + * @version 1.2 + */ + function property_is_next($istring, $pos) { + $all_properties = & $GLOBALS['csstidy']['all_properties']; + $istring = substr($istring, $pos, strlen($istring) - $pos); + $pos = strpos($istring, ':'); + if ($pos === false) { + return false; + } + $istring = strtolower(trim(substr($istring, 0, $pos))); + if (isset($all_properties[$istring])) { + $this->log('Added semicolon to the end of declaration', 'Warning'); + return true; + } + return false; + } + + /** + * Checks if a property is valid + * @param string $property + * @return bool; + * @access public + * @version 1.0 + */ + function property_is_valid($property) { + $all_properties = & $GLOBALS['csstidy']['all_properties']; + return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false ); + } + +} diff --git a/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_ctype.inc b/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_ctype.inc new file mode 100644 index 0000000..924d49d --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_ctype.inc @@ -0,0 +1,46 @@ +. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 + */ + +/** + * CSS Optimising Class + * + * This class optimises CSS data generated by csstidy. + * + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2006 + * @version 1.0 + */ +class csstidy_optimise { + + /** + * Constructor + * @param array $css contains the class csstidy + * @access private + * @version 1.0 + */ + function csstidy_optimise(&$css) { + $this->parser = & $css; + $this->css = & $css->css; + $this->sub_value = & $css->sub_value; + $this->at = & $css->at; + $this->selector = & $css->selector; + $this->property = & $css->property; + $this->value = & $css->value; + } + + /** + * Optimises $css after parsing + * @access public + * @version 1.0 + */ + function postparse() { + if ($this->parser->get_cfg('preserve_css')) { + return; + } + + if ($this->parser->get_cfg('merge_selectors') === 2) { + foreach ($this->css as $medium => $value) { + $this->merge_selectors($this->css[$medium]); + } + } + + if ($this->parser->get_cfg('discard_invalid_selectors')) { + foreach ($this->css as $medium => $value) { + $this->discard_invalid_selectors($this->css[$medium]); + } + } + + if ($this->parser->get_cfg('optimise_shorthands') > 0) { + foreach ($this->css as $medium => $value) { + foreach ($value as $selector => $value1) { + $this->css[$medium][$selector] = csstidy_optimise::merge_4value_shorthands($this->css[$medium][$selector]); + + if ($this->parser->get_cfg('optimise_shorthands') < 2) { + continue; + } + + $this->css[$medium][$selector] = csstidy_optimise::merge_font($this->css[$medium][$selector]); + + if ($this->parser->get_cfg('optimise_shorthands') < 3) { + continue; + } + + $this->css[$medium][$selector] = csstidy_optimise::merge_bg($this->css[$medium][$selector]); + if (empty($this->css[$medium][$selector])) { + unset($this->css[$medium][$selector]); + } + } + } + } + } + + /** + * Optimises values + * @access public + * @version 1.0 + */ + function value() { + $shorthands = & $GLOBALS['csstidy']['shorthands']; + + // optimise shorthand properties + if (isset($shorthands[$this->property])) { + $temp = csstidy_optimise::shorthand($this->value); // FIXME - move + if ($temp != $this->value) { + $this->parser->log('Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information'); + } + $this->value = $temp; + } + + // Remove whitespace at ! important + if ($this->value != $this->compress_important($this->value)) { + $this->parser->log('Optimised !important', 'Information'); + } + } + + /** + * Optimises shorthands + * @access public + * @version 1.0 + */ + function shorthands() { + $shorthands = & $GLOBALS['csstidy']['shorthands']; + + if (!$this->parser->get_cfg('optimise_shorthands') || $this->parser->get_cfg('preserve_css')) { + return; + } + + if ($this->property === 'font' && $this->parser->get_cfg('optimise_shorthands') > 1) { + $this->css[$this->at][$this->selector]['font'] = ''; + $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_font($this->value)); + } + if ($this->property === 'background' && $this->parser->get_cfg('optimise_shorthands') > 2) { + $this->css[$this->at][$this->selector]['background'] = ''; + $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_bg($this->value)); + } + if (isset($shorthands[$this->property])) { + $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_4value_shorthands($this->property, $this->value)); + if (is_array($shorthands[$this->property])) { + $this->css[$this->at][$this->selector][$this->property] = ''; + } + } + } + + /** + * Optimises a sub-value + * @access public + * @version 1.0 + */ + function subvalue() { + $replace_colors = & $GLOBALS['csstidy']['replace_colors']; + + $this->sub_value = trim($this->sub_value); + if ($this->sub_value == '') { // caution : '0' + return; + } + + $important = ''; + if (csstidy::is_important($this->sub_value)) { + $important = '!important'; + } + $this->sub_value = csstidy::gvw_important($this->sub_value); + + // Compress font-weight + if ($this->property === 'font-weight' && $this->parser->get_cfg('compress_font-weight')) { + if ($this->sub_value === 'bold') { + $this->sub_value = '700'; + $this->parser->log('Optimised font-weight: Changed "bold" to "700"', 'Information'); + } + else if ($this->sub_value === 'normal') { + $this->sub_value = '400'; + $this->parser->log('Optimised font-weight: Changed "normal" to "400"', 'Information'); + } + } + + $temp = $this->compress_numbers($this->sub_value); + if (strcasecmp($temp, $this->sub_value) !== 0) { + if (strlen($temp) > strlen($this->sub_value)) { + $this->parser->log('Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning'); + } + else { + $this->parser->log('Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information'); + } + $this->sub_value = $temp; + } + if ($this->parser->get_cfg('compress_colors')) { + $temp = $this->cut_color($this->sub_value); + if ($temp !== $this->sub_value) { + if (isset($replace_colors[$this->sub_value])) { + $this->parser->log('Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning'); + } + else { + $this->parser->log('Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information'); + } + $this->sub_value = $temp; + } + } + $this->sub_value .= $important; + } + + /** + * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px + * @param string $value + * @access public + * @return string + * @version 1.0 + */ + function shorthand($value) { + $important = ''; + if (csstidy::is_important($value)) { + $values = csstidy::gvw_important($value); + $important = '!important'; + } + else { + $values = $value; + } + + $values = explode(' ', $values); + switch (count($values)) { + case 4: + if ($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3]) { + return $values[0] . $important; + } + elseif ($values[1] == $values[3] && $values[0] == $values[2]) { + return $values[0] . ' ' . $values[1] . $important; + } + elseif ($values[1] == $values[3]) { + return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important; + } + break; + + case 3: + if ($values[0] == $values[1] && $values[0] == $values[2]) { + return $values[0] . $important; + } + elseif ($values[0] == $values[2]) { + return $values[0] . ' ' . $values[1] . $important; + } + break; + + case 2: + if ($values[0] == $values[1]) { + return $values[0] . $important; + } + break; + } + + return $value; + } + + /** + * Removes unnecessary whitespace in ! important + * @param string $string + * @return string + * @access public + * @version 1.1 + */ + function compress_important(&$string) { + if (csstidy::is_important($string)) { + $string = csstidy::gvw_important($string) . '!important'; + } + return $string; + } + + /** + * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. + * @param string $color + * @return string + * @version 1.1 + */ + function cut_color($color) { + $replace_colors = & $GLOBALS['csstidy']['replace_colors']; + + // rgb(0,0,0) -> #000000 (or #000 in this case later) + if (strtolower(substr($color, 0, 4)) === 'rgb(') { + $color_tmp = substr($color, 4, strlen($color) - 5); + $color_tmp = explode(',', $color_tmp); + for ($i = 0; $i < count($color_tmp); $i++) { + $color_tmp[$i] = trim($color_tmp[$i]); + if (substr($color_tmp[$i], -1) === '%') { + $color_tmp[$i] = round((255 * $color_tmp[$i]) / 100); + } + if ($color_tmp[$i] > 255) { + $color_tmp[$i] = 255; + } + } + $color = '#'; + for ($i = 0; $i < 3; $i++) { + if ($color_tmp[$i] < 16) { + $color .= '0' . dechex($color_tmp[$i]); + } + else { + $color .= dechex($color_tmp[$i]); + } + } + } + + // Fix bad color names + if (isset($replace_colors[strtolower($color)])) { + $color = $replace_colors[strtolower($color)]; + } + + // #aabbcc -> #abc + if (strlen($color) == 7) { + $color_temp = strtolower($color); + if ($color_temp{0} === '#' && $color_temp{1} == $color_temp{2} && $color_temp{3} == $color_temp{4} && $color_temp{5} == $color_temp{6}) { + $color = '#' . $color{1} . $color{3} . $color{5}; + } + } + + switch (strtolower($color)) { + /* color name -> hex code */ + case 'black': + return '#000'; + case 'fuchsia': + return '#f0f'; + case 'white': + return '#fff'; + case 'yellow': + return '#ff0'; + + /* hex code -> color name */ + case '#800000': + return 'maroon'; + case '#ffa500': + return 'orange'; + case '#808000': + return 'olive'; + case '#800080': + return 'purple'; + case '#008000': + return 'green'; + case '#000080': + return 'navy'; + case '#008080': + return 'teal'; + case '#c0c0c0': + return 'silver'; + case '#808080': + return 'gray'; + case '#f00': + return 'red'; + } + + return $color; + } + + /** + * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 ) + * @param string $subvalue + * @return string + * @version 1.2 + */ + function compress_numbers($subvalue) { + $unit_values = & $GLOBALS['csstidy']['unit_values']; + $color_values = & $GLOBALS['csstidy']['color_values']; + + // for font:1em/1em sans-serif...; + if ($this->property === 'font') { + $temp = explode('/', $subvalue); + } + else { + $temp = array($subvalue); + } + for ($l = 0; $l < count($temp); $l++) { + // if we are not dealing with a number at this point, do not optimise anything + $number = $this->AnalyseCssNumber($temp[$l]); + if ($number === false) { + return $subvalue; + } + + // Fix bad colors + if (in_array($this->property, $color_values)) { + $temp[$l] = '#' . $temp[$l]; + continue; + } + + if (abs($number[0]) > 0) { + if ($number[1] == '' && in_array($this->property, $unit_values, true)) { + $number[1] = 'px'; + } + } + else { + $number[1] = ''; + } + + $temp[$l] = $number[0] . $number[1]; + } + + return ((count($temp) > 1) ? $temp[0] . '/' . $temp[1] : $temp[0]); + } + + /** + * Checks if a given string is a CSS valid number. If it is, + * an array containing the value and unit is returned + * @param string $string + * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number + */ + function AnalyseCssNumber($string) { + // most simple checks first + if (strlen($string) == 0 || ctype_alpha($string{0})) { + return false; + } + + $units = & $GLOBALS['csstidy']['units']; + $return = array(0, ''); + + $return[0] = floatval($string); + if (abs($return[0]) > 0 && abs($return[0]) < 1) { + if ($return[0] < 0) { + $return[0] = '-' . ltrim(substr($return[0], 1), '0'); + } + else { + $return[0] = ltrim($return[0], '0'); + } + } + + // Look for unit and split from value if exists + foreach ($units as $unit) { + $expectUnitAt = strlen($string) - strlen($unit); + if (!($unitInString = stristr($string, $unit))) { // mb_strpos() fails with "false" + continue; + } + $actualPosition = strpos($string, $unitInString); + if ($expectUnitAt === $actualPosition) { + $return[1] = $unit; + $string = substr($string, 0, - strlen($unit)); + break; + } + } + if (!is_numeric($string)) { + return false; + } + return $return; + } + + /** + * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red} + * Very basic and has at least one bug. Hopefully there is a replacement soon. + * @param array $array + * @return array + * @access public + * @version 1.2 + */ + function merge_selectors(&$array) { + $css = $array; + foreach ($css as $key => $value) { + if (!isset($css[$key])) { + continue; + } + $newsel = ''; + + // Check if properties also exist in another selector + $keys = array(); + // PHP bug (?) without $css = $array; here + foreach ($css as $selector => $vali) { + if ($selector == $key) { + continue; + } + + if ($css[$key] === $vali) { + $keys[] = $selector; + } + } + + if (!empty($keys)) { + $newsel = $key; + unset($css[$key]); + foreach ($keys as $selector) { + unset($css[$selector]); + $newsel .= ',' . $selector; + } + $css[$newsel] = $value; + } + } + $array = $css; + } + + /** + * Removes invalid selectors and their corresponding rule-sets as + * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check + * and should be replaced by a full-blown parsing algorithm or + * regular expression + * @version 1.4 + */ + function discard_invalid_selectors(&$array) { + $invalid = array( + '+' => true, + '~' => true, + ',' => true, + '>' => true, + ); + foreach ($array as $selector => $decls) { + $ok = true; + $selectors = array_map('trim', explode(',', $selector)); + foreach ($selectors as $s) { + $simple_selectors = preg_split('/\s*[+>~\s]\s*/', $s); + foreach ($simple_selectors as $ss) { + if ($ss === '') { + $ok = false; + } + // could also check $ss for internal structure, + // but that probably would be too slow + } + } + if (!$ok) { + unset($array[$selector]); + } + } + } + + /** + * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;... + * @param string $property + * @param string $value + * @return array + * @version 1.0 + * @see merge_4value_shorthands() + */ + function dissolve_4value_shorthands($property, $value) { + $shorthands = & $GLOBALS['csstidy']['shorthands']; + if (!is_array($shorthands[$property])) { + $return[$property] = $value; + return $return; + } + + $important = ''; + if (csstidy::is_important($value)) { + $value = csstidy::gvw_important($value); + $important = '!important'; + } + $values = explode(' ', $value); + + + $return = array(); + if (count($values) == 4) { + for ($i = 0; $i < 4; $i++) { + $return[$shorthands[$property][$i]] = $values[$i] . $important; + } + } + elseif (count($values) == 3) { + $return[$shorthands[$property][0]] = $values[0] . $important; + $return[$shorthands[$property][1]] = $values[1] . $important; + $return[$shorthands[$property][3]] = $values[1] . $important; + $return[$shorthands[$property][2]] = $values[2] . $important; + } + elseif (count($values) == 2) { + for ($i = 0; $i < 4; $i++) { + $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1] . $important : $values[0] . $important; + } + } + else { + for ($i = 0; $i < 4; $i++) { + $return[$shorthands[$property][$i]] = $values[0] . $important; + } + } + + return $return; + } + + /** + * Explodes a string as explode() does, however, not if $sep is escaped or within a string. + * @param string $sep seperator + * @param string $string + * @return array + * @version 1.0 + */ + function explode_ws($sep, $string) { + $status = 'st'; + $to = ''; + + $output = array(); + $num = 0; + for ($i = 0, $len = strlen($string); $i < $len; $i++) { + switch ($status) { + case 'st': + if ($string{$i} == $sep && !csstidy::escaped($string, $i)) { + ++$num; + } + elseif ($string{$i} === '"' || $string{$i} === '\'' || $string{$i} === '(' && !csstidy::escaped($string, $i)) { + $status = 'str'; + $to = ($string{$i} === '(') ? ')' : $string{$i}; + (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i}; + } + else { + (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i}; + } + break; + + case 'str': + if ($string{$i} == $to && !csstidy::escaped($string, $i)) { + $status = 'st'; + } + (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i}; + break; + } + } + + if (isset($output[0])) { + return $output; + } + else { + return array($output); + } + } + + /** + * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() + * @param array $array + * @return array + * @version 1.2 + * @see dissolve_4value_shorthands() + */ + function merge_4value_shorthands($array) { + $return = $array; + $shorthands = & $GLOBALS['csstidy']['shorthands']; + + foreach ($shorthands as $key => $value) { + if (isset($array[$value[0]]) && isset($array[$value[1]]) + && isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) { + $return[$key] = ''; + + $important = ''; + for ($i = 0; $i < 4; $i++) { + $val = $array[$value[$i]]; + if (csstidy::is_important($val)) { + $important = '!important'; + $return[$key] .= csstidy::gvw_important($val) . ' '; + } + else { + $return[$key] .= $val . ' '; + } + unset($return[$value[$i]]); + } + $return[$key] = csstidy_optimise::shorthand(trim($return[$key] . $important)); + } + } + return $return; + } + + /** + * Dissolve background property + * @param string $str_value + * @return array + * @version 1.0 + * @see merge_bg() + * @todo full CSS 3 compliance + */ + function dissolve_short_bg($str_value) { + // don't try to explose background gradient ! + if (stripos($str_value, "gradient(") !== FALSE) { + return array('background' => $str_value); + } + + $background_prop_default = & $GLOBALS['csstidy']['background_prop_default']; + $repeat = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space'); + $attachment = array('scroll', 'fixed', 'local'); + $clip = array('border', 'padding'); + $origin = array('border', 'padding', 'content'); + $pos = array('top', 'center', 'bottom', 'left', 'right'); + $important = ''; + $return = array( + 'background-image' => null, + 'background-size' => null, + 'background-repeat' => null, + 'background-position' => null, + 'background-attachment' => null, + 'background-clip' => null, + 'background-origin' => null, + 'background-color' => null, + ); + + if (csstidy::is_important($str_value)) { + $important = ' !important'; + $str_value = csstidy::gvw_important($str_value); + } + + $str_value = csstidy_optimise::explode_ws(',', $str_value); + for ($i = 0; $i < count($str_value); $i++) { + $have['clip'] = false; + $have['pos'] = false; + $have['color'] = false; + $have['bg'] = false; + + if (is_array($str_value[$i])) { + $str_value[$i] = $str_value[$i][0]; + } + $str_value[$i] = csstidy_optimise::explode_ws(' ', trim($str_value[$i])); + + for ($j = 0; $j < count($str_value[$i]); $j++) { + if ($have['bg'] === false && (substr($str_value[$i][$j], 0, 4) === 'url(' || $str_value[$i][$j] === 'none')) { + $return['background-image'] .= $str_value[$i][$j] . ','; + $have['bg'] = true; + } + elseif (in_array($str_value[$i][$j], $repeat, true)) { + $return['background-repeat'] .= $str_value[$i][$j] . ','; + } + elseif (in_array($str_value[$i][$j], $attachment, true)) { + $return['background-attachment'] .= $str_value[$i][$j] . ','; + } + elseif (in_array($str_value[$i][$j], $clip, true) && !$have['clip']) { + $return['background-clip'] .= $str_value[$i][$j] . ','; + $have['clip'] = true; + } + elseif (in_array($str_value[$i][$j], $origin, true)) { + $return['background-origin'] .= $str_value[$i][$j] . ','; + } + elseif ($str_value[$i][$j]{0} === '(') { + $return['background-size'] .= substr($str_value[$i][$j], 1, -1) . ','; + } + elseif (in_array($str_value[$i][$j], $pos, true) || is_numeric($str_value[$i][$j]{0}) || $str_value[$i][$j]{0} === null || $str_value[$i][$j]{0} === '-' || $str_value[$i][$j]{0} === '.') { + $return['background-position'] .= $str_value[$i][$j]; + if (!$have['pos']) { + $return['background-position'] .= ' '; + } + else { + $return['background-position'] .= ','; + } + $have['pos'] = true; + } + elseif (!$have['color']) { + $return['background-color'] .= $str_value[$i][$j] . ','; + $have['color'] = true; + } + } + } + + foreach ($background_prop_default as $bg_prop => $default_value) { + if ($return[$bg_prop] !== null) { + $return[$bg_prop] = substr($return[$bg_prop], 0, -1) . $important; + } + else { + $return[$bg_prop] = $default_value . $important; + } + } + return $return; + } + + /** + * Merges all background properties + * @param array $input_css + * @return array + * @version 1.0 + * @see dissolve_short_bg() + * @todo full CSS 3 compliance + */ + function merge_bg($input_css) { + $background_prop_default = & $GLOBALS['csstidy']['background_prop_default']; + // Max number of background images. CSS3 not yet fully implemented + $number_of_values = @max(count(csstidy_optimise::explode_ws(',', $input_css['background-image'])), count(csstidy_optimise::explode_ws(',', $input_css['background-color'])), 1); + // Array with background images to check if BG image exists + $bg_img_array = @csstidy_optimise::explode_ws(',', csstidy::gvw_important($input_css['background-image'])); + $new_bg_value = ''; + $important = ''; + + // if background properties is here and not empty, don't try anything + if (isset($input_css['background']) AND $input_css['background']) { + return $input_css; + } + + for ($i = 0; $i < $number_of_values; $i++) { + foreach ($background_prop_default as $bg_property => $default_value) { + // Skip if property does not exist + if (!isset($input_css[$bg_property])) { + continue; + } + + $cur_value = $input_css[$bg_property]; + // skip all optimisation if gradient() somewhere + if (stripos($cur_value, "gradient(") !== FALSE) { + return $input_css; + } + + // Skip some properties if there is no background image + if ((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none') + && ($bg_property === 'background-size' || $bg_property === 'background-position' + || $bg_property === 'background-attachment' || $bg_property === 'background-repeat')) { + continue; + } + + // Remove !important + if (csstidy::is_important($cur_value)) { + $important = ' !important'; + $cur_value = csstidy::gvw_important($cur_value); + } + + // Do not add default values + if ($cur_value === $default_value) { + continue; + } + + $temp = csstidy_optimise::explode_ws(',', $cur_value); + + if (isset($temp[$i])) { + if ($bg_property === 'background-size') { + $new_bg_value .= '(' . $temp[$i] . ') '; + } + else { + $new_bg_value .= $temp[$i] . ' '; + } + } + } + + $new_bg_value = trim($new_bg_value); + if ($i != $number_of_values - 1) { + $new_bg_value .= ','; + } + } + + // Delete all background-properties + foreach ($background_prop_default as $bg_property => $default_value) { + unset($input_css[$bg_property]); + } + + // Add new background property + if ($new_bg_value !== '') { + $input_css['background'] = $new_bg_value . $important; + } + elseif (isset($input_css['background'])) { + $input_css['background'] = 'none'; + } + + return $input_css; + } + + /** + * Dissolve font property + * @param string $str_value + * @return array + * @version 1.3 + * @see merge_font() + */ + function dissolve_short_font($str_value) { + $font_prop_default = & $GLOBALS['csstidy']['font_prop_default']; + $font_weight = array('normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900); + $font_variant = array('normal', 'small-caps'); + $font_style = array('normal', 'italic', 'oblique'); + $important = ''; + $return = array( + 'font-style' => null, + 'font-variant' => null, + 'font-weight' => null, + 'font-size' => null, + 'line-height' => null, + 'font-family' => null, + ); + + if (csstidy::is_important($str_value)) { + $important = '!important'; + $str_value = csstidy::gvw_important($str_value); + } + + $have['style'] = false; + $have['variant'] = false; + $have['weight'] = false; + $have['size'] = false; + // Detects if font-family consists of several words w/o quotes + $multiwords = false; + + // Workaround with multiple font-family + $str_value = csstidy_optimise::explode_ws(',', trim($str_value)); + + $str_value[0] = csstidy_optimise::explode_ws(' ', trim($str_value[0])); + + for ($j = 0; $j < count($str_value[0]); $j++) { + if ($have['weight'] === false && in_array($str_value[0][$j], $font_weight)) { + $return['font-weight'] = $str_value[0][$j]; + $have['weight'] = true; + } + elseif ($have['variant'] === false && in_array($str_value[0][$j], $font_variant)) { + $return['font-variant'] = $str_value[0][$j]; + $have['variant'] = true; + } + elseif ($have['style'] === false && in_array($str_value[0][$j], $font_style)) { + $return['font-style'] = $str_value[0][$j]; + $have['style'] = true; + } + elseif ($have['size'] === false && (is_numeric($str_value[0][$j]{0}) || $str_value[0][$j]{0} === null || $str_value[0][$j]{0} === '.')) { + $size = csstidy_optimise::explode_ws('/', trim($str_value[0][$j])); + $return['font-size'] = $size[0]; + if (isset($size[1])) { + $return['line-height'] = $size[1]; + } + else { + $return['line-height'] = ''; // don't add 'normal' ! + } + $have['size'] = true; + } + else { + if (isset($return['font-family'])) { + $return['font-family'] .= ' ' . $str_value[0][$j]; + $multiwords = true; + } + else { + $return['font-family'] = $str_value[0][$j]; + } + } + } + // add quotes if we have several qords in font-family + if ($multiwords !== false) { + $return['font-family'] = '"' . $return['font-family'] . '"'; + } + $i = 1; + while (isset($str_value[$i])) { + $return['font-family'] .= ',' . trim($str_value[$i]); + $i++; + } + + // Fix for 100 and more font-size + if ($have['size'] === false && isset($return['font-weight']) && + is_numeric($return['font-weight']{0})) { + $return['font-size'] = $return['font-weight']; + unset($return['font-weight']); + } + + foreach ($font_prop_default as $font_prop => $default_value) { + if ($return[$font_prop] !== null) { + $return[$font_prop] = $return[$font_prop] . $important; + } + else { + $return[$font_prop] = $default_value . $important; + } + } + return $return; + } + + /** + * Merges all fonts properties + * @param array $input_css + * @return array + * @version 1.3 + * @see dissolve_short_font() + */ + function merge_font($input_css) { + $font_prop_default = & $GLOBALS['csstidy']['font_prop_default']; + $new_font_value = ''; + $important = ''; + // Skip if not font-family and font-size set + if (isset($input_css['font-family']) && isset($input_css['font-size'])) { + // fix several words in font-family - add quotes + if (isset($input_css['font-family'])) { + $families = explode(",", $input_css['font-family']); + $result_families = array(); + foreach ($families as $family) { + $family = trim($family); + $len = strlen($family); + if (strpos($family, " ") && + !(($family{0} == '"' && $family{$len - 1} == '"') || + ($family{0} == "'" && $family{$len - 1} == "'"))) { + $family = '"' . $family . '"'; + } + $result_families[] = $family; + } + $input_css['font-family'] = implode(",", $result_families); + } + foreach ($font_prop_default as $font_property => $default_value) { + + // Skip if property does not exist + if (!isset($input_css[$font_property])) { + continue; + } + + $cur_value = $input_css[$font_property]; + + // Skip if default value is used + if ($cur_value === $default_value) { + continue; + } + + // Remove !important + if (csstidy::is_important($cur_value)) { + $important = '!important'; + $cur_value = csstidy::gvw_important($cur_value); + } + + $new_font_value .= $cur_value; + // Add delimiter + $new_font_value .= ( $font_property === 'font-size' && + isset($input_css['line-height'])) ? '/' : ' '; + } + + $new_font_value = trim($new_font_value); + + // Delete all font-properties + foreach ($font_prop_default as $font_property => $default_value) { + if ($font_property !== 'font' OR !$new_font_value) { + unset($input_css[$font_property]); + } + } + + // Add new font property + if ($new_font_value !== '') { + $input_css['font'] = $new_font_value . $important; + } + } + + return $input_css; + } + +} diff --git a/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_print.inc b/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_print.inc new file mode 100644 index 0000000..c6b4263 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/csstidy/class.csstidy_print.inc @@ -0,0 +1,416 @@ +. + * + * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2007 + * @author Brett Zamir (brettz9 at yahoo dot com) 2007 + * @author Cedric Morin (cedric at yterium dot com) 2010 + */ + +/** + * CSS Printing class + * + * This class prints CSS data generated by csstidy. + * + * @package csstidy + * @author Florian Schmitz (floele at gmail dot com) 2005-2006 + * @version 1.0.1 + */ +class csstidy_print { + + /** + * Saves the input CSS string + * @var string + * @access private + */ + var $input_css = ''; + /** + * Saves the formatted CSS string + * @var string + * @access public + */ + var $output_css = ''; + /** + * Saves the formatted CSS string (plain text) + * @var string + * @access public + */ + var $output_css_plain = ''; + + /** + * Constructor + * @param array $css contains the class csstidy + * @access private + * @version 1.0 + */ + function csstidy_print(&$css) { + $this->parser = & $css; + $this->css = & $css->css; + $this->template = & $css->template; + $this->tokens = & $css->tokens; + $this->charset = & $css->charset; + $this->import = & $css->import; + $this->namespace = & $css->namespace; + } + + /** + * Resets output_css and output_css_plain (new css code) + * @access private + * @version 1.0 + */ + function _reset() { + $this->output_css = ''; + $this->output_css_plain = ''; + } + + /** + * Returns the CSS code as plain text + * @param string $default_media default @media to add to selectors without any @media + * @return string + * @access public + * @version 1.0 + */ + function plain($default_media = '') { + $this->_print(true, $default_media); + return $this->output_css_plain; + } + + /** + * Returns the formatted CSS code + * @param string $default_media default @media to add to selectors without any @media + * @return string + * @access public + * @version 1.0 + */ + function formatted($default_media = '') { + $this->_print(false, $default_media); + return $this->output_css; + } + + /** + * Returns the formatted CSS code to make a complete webpage + * @param string $doctype shorthand for the document type + * @param bool $externalcss indicates whether styles to be attached internally or as an external stylesheet + * @param string $title title to be added in the head of the document + * @param string $lang two-letter language code to be added to the output + * @return string + * @access public + * @version 1.4 + */ + function formatted_page($doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en') { + switch ($doctype) { + case 'xhtml1.0strict': + $doctype_output = ''; + break; + case 'xhtml1.1': + default: + $doctype_output = ''; + break; + } + + $output = $cssparsed = ''; + $this->output_css_plain = & $output; + + $output .= $doctype_output . "\n" . '' : ' lang="' . $lang . '">'; + $output .= "\n\n $title"; + + if ($externalcss) { + $output .= "\n "; + } + else { + $output .= "\n" . ' '; + // } + } + $output .= "\n\n"; + $output .= $this->formatted(); + $output .= '' . "\n" . ''; + return $this->output_css_plain; + } + + /** + * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain + * @param bool $plain plain text or not + * @param string $default_media default @media to add to selectors without any @media + * @access private + * @version 2.0 + */ + function _print($plain = false, $default_media = '') { + if ($this->output_css && $this->output_css_plain) { + return; + } + + $output = ''; + if (!$this->parser->get_cfg('preserve_css')) { + $this->_convert_raw_css($default_media); + } + + $template = & $this->template; + + if ($plain) { + $template = array_map('strip_tags', $template); + } + + if ($this->parser->get_cfg('timestamp')) { + array_unshift($this->tokens, array(COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . date('r') . ' ')); + } + + if (!empty($this->charset)) { + $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6]; + } + + if (!empty($this->import)) { + for ($i = 0, $size = count($this->import); $i < $size; $i++) { + if (substr($this->import[$i], 0, 4) === 'url(' && substr($this->import[$i], -1, 1) === ')') { + $this->import[$i] = '\'' . substr($this->import[$i], 4, -1) . '\''; + $this->parser->log('Optimised @import : Removed "url("', 'Information'); + } + $output .= $template[0] . '@import ' . $template[5] . $this->import[$i] . $template[6]; + } + } + + if (!empty($this->namespace)) { + if (substr($this->namespace, 0, 4) === 'url(' && substr($this->namespace, -1, 1) === ')') { + $this->namespace = '\'' . substr($this->namespace, 4, -1) . '\''; + $this->parser->log('Optimised @namespace : Removed "url("', 'Information'); + } + $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6]; + } + + $output .= $template[13]; + $in_at_out = ''; + $out = & $output; + + foreach ($this->tokens as $key => $token) { + switch ($token[0]) { + case AT_START: + $out .= $template[0] . $this->_htmlsp($token[1], $plain) . $template[1]; + $out = & $in_at_out; + break; + + case SEL_START: + if ($this->parser->get_cfg('lowercase_s')) { + $token[1] = strtolower($token[1]); + } + $out .= ( $token[1]{0} !== '@') ? $template[2] . $this->_htmlsp($token[1], $plain) : $template[0] . $this->_htmlsp($token[1], $plain); + $out .= $template[3]; + break; + + case PROPERTY: + if ($this->parser->get_cfg('case_properties') === 2) { + $token[1] = strtoupper($token[1]); + } + elseif ($this->parser->get_cfg('case_properties') === 1) { + $token[1] = strtolower($token[1]); + } + $out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5]; + break; + + case VALUE: + $out .= $this->_htmlsp($token[1], $plain); + if ($this->_seeknocomment($key, 1) == SEL_END && $this->parser->get_cfg('remove_last_;')) { + $out .= str_replace(';', '', $template[6]); + } + else { + $out .= $template[6]; + } + break; + + case SEL_END: + $out .= $template[7]; + if ($this->_seeknocomment($key, 1) != AT_END) { + $out .= $template[8]; + } + break; + + case AT_END: + $out = & $output; + $out .= $template[10] . str_replace("\n", "\n" . $template[10], $in_at_out); + $in_at_out = ''; + $out .= $template[9]; + break; + + case COMMENT: + $out .= $template[11] . '/*' . $this->_htmlsp($token[1], $plain) . '*/' . $template[12]; + break; + } + } + + $output = trim($output); + + if (!$plain) { + $this->output_css = $output; + $this->_print(true); + } + else { + // If using spaces in the template, don't want these to appear in the plain output + $this->output_css_plain = str_replace(' ', '', $output); + } + } + + /** + * Gets the next token type which is $move away from $key, excluding comments + * @param integer $key current position + * @param integer $move move this far + * @return mixed a token type + * @access private + * @version 1.0 + */ + function _seeknocomment($key, $move) { + $go = ($move > 0) ? 1 : -1; + for ($i = $key + 1; abs($key - $i) - 1 < abs($move); $i += $go) { + if (!isset($this->tokens[$i])) { + return; + } + if ($this->tokens[$i][0] == COMMENT) { + $move += 1; + continue; + } + return $this->tokens[$i][0]; + } + } + + /** + * Converts $this->css array to a raw array ($this->tokens) + * @param string $default_media default @media to add to selectors without any @media + * @access private + * @version 1.0 + */ + function _convert_raw_css($default_media = '') { + $this->tokens = array(); + + foreach ($this->css as $medium => $val) { + if ($this->parser->get_cfg('sort_selectors')) { + ksort($val); + } + if (intval($medium) < DEFAULT_AT) { + $this->parser->_add_token(AT_START, $medium, true); + } + elseif ($default_media) { + $this->parser->_add_token(AT_START, $default_media, true); + } + + foreach ($val as $selector => $vali) { + if ($this->parser->get_cfg('sort_properties')) { + ksort($vali); + } + $this->parser->_add_token(SEL_START, $selector, true); + + foreach ($vali as $property => $valj) { + $this->parser->_add_token(PROPERTY, $property, true); + $this->parser->_add_token(VALUE, $valj, true); + } + + $this->parser->_add_token(SEL_END, $selector, true); + } + + if (intval($medium) < DEFAULT_AT) { + $this->parser->_add_token(AT_END, $medium, true); + } + elseif ($default_media) { + $this->parser->_add_token(AT_END, $default_media, true); + } + } + } + + /** + * Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes print_code() cleaner. + * @param string $string + * @param bool $plain + * @return string + * @see csstidy_print::_print() + * @access private + * @version 1.0 + */ + function _htmlsp($string, $plain) { + if (!$plain) { + return htmlspecialchars($string, ENT_QUOTES, 'utf-8'); + } + return $string; + } + + /** + * Get compression ratio + * @access public + * @return float + * @version 1.2 + */ + function get_ratio() { + if (!$this->output_css_plain) { + $this->formatted(); + } + return round((strlen($this->input_css) - strlen($this->output_css_plain)) / strlen($this->input_css), 3) * 100; + } + + /** + * Get difference between the old and new code in bytes and prints the code if necessary. + * @access public + * @return string + * @version 1.1 + */ + function get_diff() { + if (!$this->output_css_plain) { + $this->formatted(); + } + + $diff = strlen($this->output_css_plain) - strlen($this->input_css); + + if ($diff > 0) { + return '+' . $diff; + } + elseif ($diff == 0) { + return '+-' . $diff; + } + + return $diff; + } + + /** + * Get the size of either input or output CSS in KB + * @param string $loc default is "output" + * @access public + * @return integer + * @version 1.0 + */ + function size($loc = 'output') { + if ($loc === 'output' && !$this->output_css) { + $this->formatted(); + } + + if ($loc === 'input') { + return (strlen($this->input_css) / 1000); + } + else { + return (strlen($this->output_css_plain) / 1000); + } + } + +} diff --git a/sites/all/modules/advagg/advagg_css_compress/csstidy/data.inc b/sites/all/modules/advagg/advagg_css_compress/csstidy/data.inc new file mode 100644 index 0000000..b996e46 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/csstidy/data.inc @@ -0,0 +1,528 @@ +?[]^`|~'; + +/** + * All CSS units (CSS 3 units included) + * + * @see compress_numbers() + * @global array $GLOBALS['csstidy']['units'] + * @version 1.0 + */ +$GLOBALS['csstidy']['units'] = array('in', 'cm', 'mm', 'pt', 'pc', 'px', 'rem', 'em', '%', 'ex', 'gd', 'vw', 'vh', 'vm', 'deg', 'grad', 'rad', 'ms', 's', 'khz', 'hz'); + +/** + * Available at-rules + * + * @global array $GLOBALS['csstidy']['at_rules'] + * @version 1.0 + */ +$GLOBALS['csstidy']['at_rules'] = array( + 'page' => 'is', + 'font-face' => 'is', + 'charset' => 'iv', + 'import' => 'iv', + 'namespace' => 'iv', + 'media' => 'at', +); + +/** + * Properties that need a value with unit + * + * @todo CSS3 properties + * @see compress_numbers(); + * @global array $GLOBALS['csstidy']['unit_values'] + * @version 1.2 + */ +$GLOBALS['csstidy']['unit_values'] = array( + 'background', + 'background-position', + 'border', + 'border-top', + 'border-right', + 'border-bottom', + 'border-left', + 'border-width', + 'border-top-width', + 'border-right-width', + 'border-left-width', + 'border-bottom-width', + 'bottom', + 'border-spacing', + 'font-size', + 'height', + 'left', + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + 'max-height', + 'max-width', + 'min-height', + 'min-width', + 'outline', + 'outline-width', + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'right', + 'top', + 'text-indent', + 'letter-spacing', + 'word-spacing', + 'width', +); + +/** + * Properties that allow as value + * + * @todo CSS3 properties + * @see compress_numbers(); + * @global array $GLOBALS['csstidy']['color_values'] + * @version 1.0 + */ +$GLOBALS['csstidy']['color_values'] = array(); +$GLOBALS['csstidy']['color_values'][] = 'background-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-top-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-right-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-bottom-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-left-color'; +$GLOBALS['csstidy']['color_values'][] = 'color'; +$GLOBALS['csstidy']['color_values'][] = 'outline-color'; + +/** + * Default values for the background properties + * + * @todo Possibly property names will change during CSS3 development + * @global array $GLOBALS['csstidy']['background_prop_default'] + * @see dissolve_short_bg() + * @see merge_bg() + * @version 1.0 + */ +$GLOBALS['csstidy']['background_prop_default'] = array(); +$GLOBALS['csstidy']['background_prop_default']['background-image'] = 'none'; +$GLOBALS['csstidy']['background_prop_default']['background-size'] = 'auto'; +$GLOBALS['csstidy']['background_prop_default']['background-repeat'] = 'repeat'; +$GLOBALS['csstidy']['background_prop_default']['background-position'] = '0 0'; +$GLOBALS['csstidy']['background_prop_default']['background-attachment'] = 'scroll'; +$GLOBALS['csstidy']['background_prop_default']['background-clip'] = 'border'; +$GLOBALS['csstidy']['background_prop_default']['background-origin'] = 'padding'; +$GLOBALS['csstidy']['background_prop_default']['background-color'] = 'transparent'; + +/** + * Default values for the font properties + * + * @global array $GLOBALS['csstidy']['font_prop_default'] + * @see merge_fonts() + * @version 1.3 + */ +$GLOBALS['csstidy']['font_prop_default'] = array(); +$GLOBALS['csstidy']['font_prop_default']['font-style'] = 'normal'; +$GLOBALS['csstidy']['font_prop_default']['font-variant'] = 'normal'; +$GLOBALS['csstidy']['font_prop_default']['font-weight'] = 'normal'; +$GLOBALS['csstidy']['font_prop_default']['font-size'] = ''; +$GLOBALS['csstidy']['font_prop_default']['line-height'] = ''; +$GLOBALS['csstidy']['font_prop_default']['font-family'] = ''; + +/** + * A list of non-W3C color names which get replaced by their hex-codes + * + * @global array $GLOBALS['csstidy']['replace_colors'] + * @see cut_color() + * @version 1.0 + */ +$GLOBALS['csstidy']['replace_colors'] = array(); +$GLOBALS['csstidy']['replace_colors']['aliceblue'] = '#f0f8ff'; +$GLOBALS['csstidy']['replace_colors']['antiquewhite'] = '#faebd7'; +$GLOBALS['csstidy']['replace_colors']['aquamarine'] = '#7fffd4'; +$GLOBALS['csstidy']['replace_colors']['azure'] = '#f0ffff'; +$GLOBALS['csstidy']['replace_colors']['beige'] = '#f5f5dc'; +$GLOBALS['csstidy']['replace_colors']['bisque'] = '#ffe4c4'; +$GLOBALS['csstidy']['replace_colors']['blanchedalmond'] = '#ffebcd'; +$GLOBALS['csstidy']['replace_colors']['blueviolet'] = '#8a2be2'; +$GLOBALS['csstidy']['replace_colors']['brown'] = '#a52a2a'; +$GLOBALS['csstidy']['replace_colors']['burlywood'] = '#deb887'; +$GLOBALS['csstidy']['replace_colors']['cadetblue'] = '#5f9ea0'; +$GLOBALS['csstidy']['replace_colors']['chartreuse'] = '#7fff00'; +$GLOBALS['csstidy']['replace_colors']['chocolate'] = '#d2691e'; +$GLOBALS['csstidy']['replace_colors']['coral'] = '#ff7f50'; +$GLOBALS['csstidy']['replace_colors']['cornflowerblue'] = '#6495ed'; +$GLOBALS['csstidy']['replace_colors']['cornsilk'] = '#fff8dc'; +$GLOBALS['csstidy']['replace_colors']['crimson'] = '#dc143c'; +$GLOBALS['csstidy']['replace_colors']['cyan'] = '#00ffff'; +$GLOBALS['csstidy']['replace_colors']['darkblue'] = '#00008b'; +$GLOBALS['csstidy']['replace_colors']['darkcyan'] = '#008b8b'; +$GLOBALS['csstidy']['replace_colors']['darkgoldenrod'] = '#b8860b'; +$GLOBALS['csstidy']['replace_colors']['darkgray'] = '#a9a9a9'; +$GLOBALS['csstidy']['replace_colors']['darkgreen'] = '#006400'; +$GLOBALS['csstidy']['replace_colors']['darkkhaki'] = '#bdb76b'; +$GLOBALS['csstidy']['replace_colors']['darkmagenta'] = '#8b008b'; +$GLOBALS['csstidy']['replace_colors']['darkolivegreen'] = '#556b2f'; +$GLOBALS['csstidy']['replace_colors']['darkorange'] = '#ff8c00'; +$GLOBALS['csstidy']['replace_colors']['darkorchid'] = '#9932cc'; +$GLOBALS['csstidy']['replace_colors']['darkred'] = '#8b0000'; +$GLOBALS['csstidy']['replace_colors']['darksalmon'] = '#e9967a'; +$GLOBALS['csstidy']['replace_colors']['darkseagreen'] = '#8fbc8f'; +$GLOBALS['csstidy']['replace_colors']['darkslateblue'] = '#483d8b'; +$GLOBALS['csstidy']['replace_colors']['darkslategray'] = '#2f4f4f'; +$GLOBALS['csstidy']['replace_colors']['darkturquoise'] = '#00ced1'; +$GLOBALS['csstidy']['replace_colors']['darkviolet'] = '#9400d3'; +$GLOBALS['csstidy']['replace_colors']['deeppink'] = '#ff1493'; +$GLOBALS['csstidy']['replace_colors']['deepskyblue'] = '#00bfff'; +$GLOBALS['csstidy']['replace_colors']['dimgray'] = '#696969'; +$GLOBALS['csstidy']['replace_colors']['dodgerblue'] = '#1e90ff'; +$GLOBALS['csstidy']['replace_colors']['feldspar'] = '#d19275'; +$GLOBALS['csstidy']['replace_colors']['firebrick'] = '#b22222'; +$GLOBALS['csstidy']['replace_colors']['floralwhite'] = '#fffaf0'; +$GLOBALS['csstidy']['replace_colors']['forestgreen'] = '#228b22'; +$GLOBALS['csstidy']['replace_colors']['gainsboro'] = '#dcdcdc'; +$GLOBALS['csstidy']['replace_colors']['ghostwhite'] = '#f8f8ff'; +$GLOBALS['csstidy']['replace_colors']['gold'] = '#ffd700'; +$GLOBALS['csstidy']['replace_colors']['goldenrod'] = '#daa520'; +$GLOBALS['csstidy']['replace_colors']['greenyellow'] = '#adff2f'; +$GLOBALS['csstidy']['replace_colors']['honeydew'] = '#f0fff0'; +$GLOBALS['csstidy']['replace_colors']['hotpink'] = '#ff69b4'; +$GLOBALS['csstidy']['replace_colors']['indianred'] = '#cd5c5c'; +$GLOBALS['csstidy']['replace_colors']['indigo'] = '#4b0082'; +$GLOBALS['csstidy']['replace_colors']['ivory'] = '#fffff0'; +$GLOBALS['csstidy']['replace_colors']['khaki'] = '#f0e68c'; +$GLOBALS['csstidy']['replace_colors']['lavender'] = '#e6e6fa'; +$GLOBALS['csstidy']['replace_colors']['lavenderblush'] = '#fff0f5'; +$GLOBALS['csstidy']['replace_colors']['lawngreen'] = '#7cfc00'; +$GLOBALS['csstidy']['replace_colors']['lemonchiffon'] = '#fffacd'; +$GLOBALS['csstidy']['replace_colors']['lightblue'] = '#add8e6'; +$GLOBALS['csstidy']['replace_colors']['lightcoral'] = '#f08080'; +$GLOBALS['csstidy']['replace_colors']['lightcyan'] = '#e0ffff'; +$GLOBALS['csstidy']['replace_colors']['lightgoldenrodyellow'] = '#fafad2'; +$GLOBALS['csstidy']['replace_colors']['lightgrey'] = '#d3d3d3'; +$GLOBALS['csstidy']['replace_colors']['lightgreen'] = '#90ee90'; +$GLOBALS['csstidy']['replace_colors']['lightpink'] = '#ffb6c1'; +$GLOBALS['csstidy']['replace_colors']['lightsalmon'] = '#ffa07a'; +$GLOBALS['csstidy']['replace_colors']['lightseagreen'] = '#20b2aa'; +$GLOBALS['csstidy']['replace_colors']['lightskyblue'] = '#87cefa'; +$GLOBALS['csstidy']['replace_colors']['lightslateblue'] = '#8470ff'; +$GLOBALS['csstidy']['replace_colors']['lightslategray'] = '#778899'; +$GLOBALS['csstidy']['replace_colors']['lightsteelblue'] = '#b0c4de'; +$GLOBALS['csstidy']['replace_colors']['lightyellow'] = '#ffffe0'; +$GLOBALS['csstidy']['replace_colors']['limegreen'] = '#32cd32'; +$GLOBALS['csstidy']['replace_colors']['linen'] = '#faf0e6'; +$GLOBALS['csstidy']['replace_colors']['magenta'] = '#ff00ff'; +$GLOBALS['csstidy']['replace_colors']['mediumaquamarine'] = '#66cdaa'; +$GLOBALS['csstidy']['replace_colors']['mediumblue'] = '#0000cd'; +$GLOBALS['csstidy']['replace_colors']['mediumorchid'] = '#ba55d3'; +$GLOBALS['csstidy']['replace_colors']['mediumpurple'] = '#9370d8'; +$GLOBALS['csstidy']['replace_colors']['mediumseagreen'] = '#3cb371'; +$GLOBALS['csstidy']['replace_colors']['mediumslateblue'] = '#7b68ee'; +$GLOBALS['csstidy']['replace_colors']['mediumspringgreen'] = '#00fa9a'; +$GLOBALS['csstidy']['replace_colors']['mediumturquoise'] = '#48d1cc'; +$GLOBALS['csstidy']['replace_colors']['mediumvioletred'] = '#c71585'; +$GLOBALS['csstidy']['replace_colors']['midnightblue'] = '#191970'; +$GLOBALS['csstidy']['replace_colors']['mintcream'] = '#f5fffa'; +$GLOBALS['csstidy']['replace_colors']['mistyrose'] = '#ffe4e1'; +$GLOBALS['csstidy']['replace_colors']['moccasin'] = '#ffe4b5'; +$GLOBALS['csstidy']['replace_colors']['navajowhite'] = '#ffdead'; +$GLOBALS['csstidy']['replace_colors']['oldlace'] = '#fdf5e6'; +$GLOBALS['csstidy']['replace_colors']['olivedrab'] = '#6b8e23'; +$GLOBALS['csstidy']['replace_colors']['orangered'] = '#ff4500'; +$GLOBALS['csstidy']['replace_colors']['orchid'] = '#da70d6'; +$GLOBALS['csstidy']['replace_colors']['palegoldenrod'] = '#eee8aa'; +$GLOBALS['csstidy']['replace_colors']['palegreen'] = '#98fb98'; +$GLOBALS['csstidy']['replace_colors']['paleturquoise'] = '#afeeee'; +$GLOBALS['csstidy']['replace_colors']['palevioletred'] = '#d87093'; +$GLOBALS['csstidy']['replace_colors']['papayawhip'] = '#ffefd5'; +$GLOBALS['csstidy']['replace_colors']['peachpuff'] = '#ffdab9'; +$GLOBALS['csstidy']['replace_colors']['peru'] = '#cd853f'; +$GLOBALS['csstidy']['replace_colors']['pink'] = '#ffc0cb'; +$GLOBALS['csstidy']['replace_colors']['plum'] = '#dda0dd'; +$GLOBALS['csstidy']['replace_colors']['powderblue'] = '#b0e0e6'; +$GLOBALS['csstidy']['replace_colors']['rosybrown'] = '#bc8f8f'; +$GLOBALS['csstidy']['replace_colors']['royalblue'] = '#4169e1'; +$GLOBALS['csstidy']['replace_colors']['saddlebrown'] = '#8b4513'; +$GLOBALS['csstidy']['replace_colors']['salmon'] = '#fa8072'; +$GLOBALS['csstidy']['replace_colors']['sandybrown'] = '#f4a460'; +$GLOBALS['csstidy']['replace_colors']['seagreen'] = '#2e8b57'; +$GLOBALS['csstidy']['replace_colors']['seashell'] = '#fff5ee'; +$GLOBALS['csstidy']['replace_colors']['sienna'] = '#a0522d'; +$GLOBALS['csstidy']['replace_colors']['skyblue'] = '#87ceeb'; +$GLOBALS['csstidy']['replace_colors']['slateblue'] = '#6a5acd'; +$GLOBALS['csstidy']['replace_colors']['slategray'] = '#708090'; +$GLOBALS['csstidy']['replace_colors']['snow'] = '#fffafa'; +$GLOBALS['csstidy']['replace_colors']['springgreen'] = '#00ff7f'; +$GLOBALS['csstidy']['replace_colors']['steelblue'] = '#4682b4'; +$GLOBALS['csstidy']['replace_colors']['tan'] = '#d2b48c'; +$GLOBALS['csstidy']['replace_colors']['thistle'] = '#d8bfd8'; +$GLOBALS['csstidy']['replace_colors']['tomato'] = '#ff6347'; +$GLOBALS['csstidy']['replace_colors']['turquoise'] = '#40e0d0'; +$GLOBALS['csstidy']['replace_colors']['violet'] = '#ee82ee'; +$GLOBALS['csstidy']['replace_colors']['violetred'] = '#d02090'; +$GLOBALS['csstidy']['replace_colors']['wheat'] = '#f5deb3'; +$GLOBALS['csstidy']['replace_colors']['whitesmoke'] = '#f5f5f5'; +$GLOBALS['csstidy']['replace_colors']['yellowgreen'] = '#9acd32'; + +/** + * A list of all shorthand properties that are devided into four properties and/or have four subvalues + * + * @global array $GLOBALS['csstidy']['shorthands'] + * @todo Are there new ones in CSS3? + * @see dissolve_4value_shorthands() + * @see merge_4value_shorthands() + * @version 1.0 + */ +$GLOBALS['csstidy']['shorthands'] = array(); +$GLOBALS['csstidy']['shorthands']['border-color'] = array('border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'); +$GLOBALS['csstidy']['shorthands']['border-style'] = array('border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'); +$GLOBALS['csstidy']['shorthands']['border-width'] = array('border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'); +$GLOBALS['csstidy']['shorthands']['margin'] = array('margin-top', 'margin-right', 'margin-bottom', 'margin-left'); +$GLOBALS['csstidy']['shorthands']['padding'] = array('padding-top', 'padding-right', 'padding-bottom', 'padding-left'); +$GLOBALS['csstidy']['shorthands']['-moz-border-radius'] = 0; + +/** + * All CSS Properties. Needed for csstidy::property_is_next() + * + * @global array $GLOBALS['csstidy']['all_properties'] + * @todo Add CSS3 properties + * @version 1.0 + * @see csstidy::property_is_next() + */ +$GLOBALS['csstidy']['all_properties'] = array(); +$GLOBALS['csstidy']['all_properties']['background'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-color'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-image'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-position'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-color'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-style'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-collapse'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-spacing'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['bottom'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['caption-side'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['content'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['clear'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['clip'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['color'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['counter-reset'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['counter-increment'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cursor'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['empty-cells'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['display'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['direction'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['float'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-family'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-stretch'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['font-size-adjust'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['font-size'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['line-height'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['marks'] = 'CSS1.0,CSS2.0'; +$GLOBALS['csstidy']['all_properties']['marker-offset'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['max-height'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['max-width'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['min-height'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['min-width'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['overflow'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['orphans'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline-width'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page-break-inside'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['position'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['quotes'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['right'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['size'] = 'CSS1.0,CSS2.0'; +$GLOBALS['csstidy']['all_properties']['speak-header'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['table-layout'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-align'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-shadow'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['white-space'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['unicode-bidi'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['visibility'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['widows'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['z-index'] = 'CSS1.0,CSS2.0,CSS2.1'; +/* Speech */ +$GLOBALS['csstidy']['all_properties']['volume'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speak'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pause'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pause-before'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pause-after'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cue'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cue-before'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cue-after'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['play-during'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['azimuth'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['elevation'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speech-rate'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['voice-family'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pitch'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pitch-range'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['stress'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['richness'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speak-punctuation'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speak-numeral'] = 'CSS2.0,CSS2.1'; + +/** + * An array containing all predefined templates. + * + * @global array $GLOBALS['csstidy']['predefined_templates'] + * @version 1.0 + * @see csstidy::load_template() + */ +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string before @rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ' {' . "\n"; //bracket after @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string before selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = ' {' . "\n"; //bracket after selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string before property +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string after property+before value +$GLOBALS['csstidy']['predefined_templates']['default'][] = ';' . "\n"; //string after value +$GLOBALS['csstidy']['predefined_templates']['default'][] = '}'; //closing bracket - selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n\n"; //space between blocks {...} +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n" . '}' . "\n\n"; //closing bracket @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //indent in @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['default'][] = '' . "\n"; // after comment +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n"; // after last line @-rule + +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ' {' . "\n"; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '{'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ';'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n"; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n" . '}' . "\n" . ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; // after comment +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n"; + +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '{'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '{'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ';'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; // after comment +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; + +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' {' . "\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '' . "\n" . '{' . "\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' '; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ';' . "\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n" . '}' . "\n\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' '; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '' . "\n"; // after comment +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n"; diff --git a/sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc b/sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc new file mode 100644 index 0000000..aad6b23 --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/yui/CSSMin.inc @@ -0,0 +1,576 @@ +raisePhpSettingLimits(); + } + } + + /** + * Minify a string of CSS + * @param string $css + * @param int|bool $linebreak_pos + * @return string + */ + public function run($css, $linebreak_pos = FALSE) + { + $this->comments = array(); + $this->preserved_tokens = array(); + + $start_index = 0; + $length = strlen($css); + + $css = $this->extract_data_urls($css); + + // collect all comment blocks... + while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) { + $end_index = $this->index_of($css, '*/', $start_index + 2); + if ($end_index < 0) { + $end_index = $length; + } + $this->comments[] = $this->str_slice($css, $start_index + 2, $end_index); + $css = $this->str_slice($css, 0, $start_index + 2) . '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_' . (count($this->comments) - 1) . '___' . $this->str_slice($css, $end_index); + $start_index += 2; + } + + // preserve strings so their content doesn't get accidentally minified + $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/", array($this, 'callback_one'), $css); + + + // Let's divide css code in chunks of 25.000 chars aprox. + // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit" of 100.000 chars by default (php < 5.3.7) + // so if we're dealing with really long strings and a (sub)pattern matches a number of chars greater than + // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently returning NULL and + // $css would be empty. + $charset = ''; + $charset_regexp = '/@charset [^;]+;/i'; + $css_chunks = array(); + $css_chunk_length = 25000; // aprox size, not exact + $start_index = 0; + $i = $css_chunk_length; // save initial iterations + $l = strlen($css); + + + // if the number of characters is 25000 or less, do not chunk + if ($l <= $css_chunk_length) { + $css_chunks[] = $css; + } else { + // chunk css code securely + while ($i < $l) { + $i += 50; // save iterations. 500 checks for a closing curly brace } + if ($l - $start_index <= $css_chunk_length || $i >= $l) { + $css_chunks[] = $this->str_slice($css, $start_index); + break; + } + if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) { + // If there are two ending curly braces }} separated or not by spaces, + // join them in the same chunk (i.e. @media blocks) + $next_chunk = substr($css, $i); + if (preg_match('/^\s*\}/', $next_chunk)) { + $i = $i + $this->index_of($next_chunk, '}') + 1; + } + + $css_chunks[] = $this->str_slice($css, $start_index, $i); + $start_index = $i; + } + } + } + + // Minify each chunk + for ($i = 0, $n = count($css_chunks); $i < $n; $i++) { + $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos); + // If there is a @charset in a css chunk... + if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) { + // delete all of them no matter the chunk + $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]); + $charset = $matches[0]; + } + } + + // Update the first chunk and put the charset to the top of the file. + $css_chunks[0] = $charset . $css_chunks[0]; + + return implode('', $css_chunks); + } + + /** + * Get the minimum PHP setting values suggested for CSSmin + * @return array + */ + public function getSuggestedPhpLimits() + { + return array( + 'memory_limit' => '128M', + 'pcre.backtrack_limit' => 1000 * 1000, + 'pcre.recursion_limit' => 500 * 1000, + ); + } + + /** + * Configure PHP to use at least the suggested minimum settings + * + * @todo Move this functionality to separate class. + */ + public function raisePhpSettingLimits() + { + foreach ($this->getSuggestedPhpLimits() as $key => $val) { + $current = $this->normalizeInt(ini_get($key)); + $suggested = $this->normalizeInt($val); + if ($current < $suggested) { + ini_set($key, $val); + } + } + } + + /** + * Does bulk of the minification + * @param string $css + * @param int|bool $linebreak_pos + * @return string + */ + private function minify($css, $linebreak_pos) + { + // strings are safe, now wrestle the comments + for ($i = 0, $max = count($this->comments); $i < $max; $i++) { + + $token = $this->comments[$i]; + $placeholder = '/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_' . $i . '___/'; + + // ! in the first position of the comment means preserve + // so push to the preserved tokens keeping the ! + if (substr($token, 0, 1) === '!') { + $this->preserved_tokens[] = $token; + $css = preg_replace($placeholder, '___YUICSSMIN_PRESERVED_TOKEN_' . (count($this->preserved_tokens) - 1) . '___', $css, 1); + continue; + } + + // \ in the last position looks like hack for Mac/IE5 + // shorten that to /*\*/ and the next one to /**/ + if (substr($token, (strlen($token) - 1), 1) === '\\') { + $this->preserved_tokens[] = '\\'; + $css = preg_replace($placeholder, '___YUICSSMIN_PRESERVED_TOKEN_' . (count($this->preserved_tokens) - 1) . '___', $css, 1); + $i = $i + 1; // attn: advancing the loop + $this->preserved_tokens[] = ''; + $css = preg_replace('/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_' . $i . '___/', '___YUICSSMIN_PRESERVED_TOKEN_' . (count($this->preserved_tokens) - 1) . '___', $css, 1); + continue; + } + + // keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (strlen($token) === 0) { + $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1)); + if ($start_index > 2) { + if (substr($css, $start_index - 3, 1) === '>') { + $this->preserved_tokens[] = ''; + $css = preg_replace($placeholder, '___YUICSSMIN_PRESERVED_TOKEN_' . (count($this->preserved_tokens) - 1) . '___', $css, 1); + } + } + } + + // in all other cases kill the comment + $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1); + } + + + // Normalize all whitespace strings to single spaces. Easier to work with that way. + $css = preg_replace('/\s+/', ' ', $css); + + // Remove the spaces before the things that should not have spaces before them. + // But, be careful not to turn "p :link {...}" into "p:link{...}" + // Swap out any pseudo-class colons with the token, and then swap back. + $css = preg_replace_callback('/(?:^|\})(?:(?:[^\{\:])+\:)+(?:[^\{]*\{)/', array($this, 'callback_two'), $css); + + $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\],])/', '$1', $css); + $css = preg_replace('/___YUICSSMIN_PSEUDOCLASSCOLON___/', ':', $css); + + // retain space for special IE6 cases + $css = preg_replace('/\:first\-(line|letter)(\{|,)/', ':first-$1 $2', $css); + + // no space after the end of a preserved comment + $css = preg_replace('/\*\/ /', '*/', $css); + + // Put the space back in some cases, to support stuff like + // @media screen and (-webkit-min-device-pixel-ratio:0){ + $css = preg_replace('/\band\(/i', 'and (', $css); + + // Remove the spaces after the things that should not have spaces after them. + $css = preg_replace('/([\!\{\}\:;\>\+\(\[,])\s+/', '$1', $css); + + // remove unnecessary semicolons + $css = preg_replace('/;+\}/', '}', $css); + + // Replace 0(px,em,%) with 0. + $css = preg_replace('/([\s\:])(0)(?:px|em|%|in|cm|mm|pc|pt|ex)/i', '$1$2', $css); + + // Replace 0 0 0 0; with 0. + $css = preg_replace('/\:0 0 0 0(;|\})/', ':0$1', $css); + $css = preg_replace('/\:0 0 0(;|\})/', ':0$1', $css); + $css = preg_replace('/\:0 0(;|\})/', ':0$1', $css); + + // Replace background-position:0; with background-position:0 0; + // same for transform-origin + $css = preg_replace_callback('/(background\-position|transform\-origin|webkit\-transform\-origin|moz\-transform\-origin|o-transform\-origin|ms\-transform\-origin)\:0(;|\})/i', array($this, 'callback_three'), $css); + + // Replace 0.6 to .6, but only when preceded by : or a white-space + $css = preg_replace('/(\:|\s)0+\.(\d+)/', '$1.$2', $css); + + // Shorten colors from rgb(51,102,153) to #336699 + // This makes it more likely that it'll get further compressed in the next step. + $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s]+)\s*\)/i', array($this, 'callback_four'), $css); + + // Shorten colors from #AABBCC to #ABC. + $css = $this->compress_hex_colors($css); + + // border: none -> border:0 + $css = preg_replace_callback('/(border|border\-top|border\-right|border\-bottom|border\-right|outline|background)\:none(;|\})/i', array($this, 'callback_five'), $css); + + // shorter opacity IE filter + $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css); + + // Remove empty rules. + $css = preg_replace('/[^\};\{\/]+\{\}/', '', $css); + + // Some source control tools don't like it when files containing lines longer + // than, say 8000 characters, are checked in. The linebreak option is used in + // that case to split long lines after a specific column. + if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) { + $linebreak_pos = (int) $linebreak_pos; + $start_index = $i = 0; + while ($i < strlen($css)) { + $i++; + if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) { + $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i); + $start_index = $i; + } + } + } + + // Replace multiple semi-colons in a row by a single one + // See SF bug #1980989 + $css = preg_replace('/;;+/', ';', $css); + + // restore preserved comments and strings + for ($i = 0, $max = count($this->preserved_tokens); $i < $max; $i++) { + $css = preg_replace('/___YUICSSMIN_PRESERVED_TOKEN_' . $i . '___/', $this->preserved_tokens[$i], $css, 1); + } + + // Trim the final string (for any leading or trailing white spaces) + $css = preg_replace('/^\s+|\s+$/', '', $css); + + return $css; + } + + /** + * Utility method to replace all data urls with tokens before we start + * compressing, to avoid performance issues running some of the subsequent + * regexes against large strings chunks. + * + * @param string $css + * @return string + */ + private function extract_data_urls($css) + { + // Leave data urls alone to increase parse performance. + $max_index = strlen($css) - 1; + $append_index = $index = $last_index = $offset = 0; + $sb = array(); + $pattern = '/url\(\s*(["\']?)data\:/'; + + // Since we need to account for non-base64 data urls, we need to handle + // ' and ) being part of the data string. Hence switching to indexOf, + // to determine whether or not we have matching string terminators and + // handling sb appends directly, instead of using matcher.append* methods. + + while (preg_match($pattern, $css, $m, 0, $offset)) { + $index = $this->index_of($css, $m[0], $offset); + $last_index = $index + strlen($m[0]); + $start_index = $index + 4; // "url(".length() + $end_index = $last_index - 1; + $terminator = $m[1]; // ', " or empty (not quoted) + $found_terminator = FALSE; + + if (strlen($terminator) === 0) { + $terminator = ')'; + } + + while ($found_terminator === FALSE && $end_index+1 <= $max_index) { + $end_index = $this->index_of($css, $terminator, $end_index + 1); + + // endIndex == 0 doesn't really apply here + if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') { + $found_terminator = TRUE; + if (')' != $terminator) { + $end_index = $this->index_of($css, ')', $end_index); + } + } + } + + // Enough searching, start moving stuff over to the buffer + $sb[] = $this->substring($css, $append_index, $index); + + if ($found_terminator) { + $token = $this->substring($css, $start_index, $end_index); + $token = preg_replace('/\s+/', '', $token); + $this->preserved_tokens[] = $token; + + $preserver = 'url(___YUICSSMIN_PRESERVED_TOKEN_' . (count($this->preserved_tokens) - 1) . '___)'; + $sb[] = $preserver; + + $append_index = $end_index + 1; + } else { + // No end terminator found, re-add the whole match. Should we throw/warn here? + $sb[] = $this->substring($css, $index, $last_index); + $append_index = $last_index; + } + + $offset = $last_index; + } + + $sb[] = $this->substring($css, $append_index); + + return implode('', $sb); + } + + /** + * Utility method to compress hex color values of the form #AABBCC to #ABC. + * + * DOES NOT compress CSS ID selectors which match the above pattern (which would break things). + * e.g. #AddressForm { ... } + * + * DOES NOT compress IE filters, which have hex color values (which would break things). + * e.g. filter: chroma(color="#FFFFFF"); + * + * DOES NOT compress invalid hex values. + * e.g. background-color: #aabbccdd + * + * @param string $css + * @return string + */ + private function compress_hex_colors($css) + { + // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters) + $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/i'; + $_index = $index = $last_index = $offset = 0; + $sb = array(); + + while (preg_match($pattern, $css, $m, 0, $offset)) { + $index = $this->index_of($css, $m[0], $offset); + $last_index = $index + strlen($m[0]); + $is_filter = (bool) $m[1]; + + $sb[] = $this->substring($css, $_index, $index); + + if ($is_filter) { + // Restore, maintain case, otherwise filter will break + $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; + } else { + if (strtolower($m[2]) == strtolower($m[3]) && + strtolower($m[4]) == strtolower($m[5]) && + strtolower($m[6]) == strtolower($m[7])) { + // Compress. + $sb[] = '#' . strtolower($m[3] . $m[5] . $m[7]); + } else { + // Non compressible color, restore but lower case. + $sb[] = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); + } + } + + $_index = $offset = $last_index - strlen($m[8]); + } + + $sb[] = $this->substring($css, $_index); + + return implode('', $sb); + } + + /* CALLBACKS + * --------------------------------------------------------------------------------------------- + */ + + private function callback_one($matches) + { + $match = $matches[0]; + $quote = substr($match, 0, 1); + // Must use addcslashes in PHP to avoid parsing of backslashes + $match = addcslashes($this->str_slice($match, 1, -1), '\\'); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (($pos = $this->index_of($match, '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_')) >= 0) { + for ($i = 0, $max = count($this->comments); $i < $max; $i++) { + $match = preg_replace('/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_' . $i . '___/', $this->comments[$i], $match, 1); + } + } + + // minify alpha opacity in filter strings + $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match); + + $this->preserved_tokens[] = $match; + return $quote . '___YUICSSMIN_PRESERVED_TOKEN_' . (count($this->preserved_tokens) - 1) . '___' . $quote; + } + + private function callback_two($matches) + { + return preg_replace('/\:/', '___YUICSSMIN_PSEUDOCLASSCOLON___', $matches[0]); + } + + private function callback_three($matches) + { + return strtolower($matches[1]) . ':0 0' . $matches[2]; + } + + private function callback_four($matches) + { + $rgbcolors = explode(',', $matches[1]); + for ($i = 0; $i < count($rgbcolors); $i++) { + $rgbcolors[$i] = base_convert(strval(intval($rgbcolors[$i], 10)), 10, 16); + if (strlen($rgbcolors[$i]) === 1) { + $rgbcolors[$i] = '0' . $rgbcolors[$i]; + } + } + return '#' . implode('', $rgbcolors); + } + + private function callback_five($matches) + { + return strtolower($matches[1]) . ':0' . $matches[2]; + } + + /* HELPERS + * --------------------------------------------------------------------------------------------- + */ + + /** + * PHP port of Javascript's "indexOf" function for strings only + * Author: Tubal Martin http://blog.margenn.com + * + * @param string $haystack + * @param string $needle + * @param int $offset index (optional) + * @return int + */ + private function index_of($haystack, $needle, $offset = 0) + { + $index = strpos($haystack, $needle, $offset); + + return ($index !== FALSE) ? $index : -1; + } + + /** + * PHP port of Javascript's "substring" function + * Author: Tubal Martin http://blog.margenn.com + * Tests: http://margenn.com/tubal/substring/ + * + * @param string $str + * @param int $from index + * @param int|bool $to index (optional) + * @return string + */ + private function substring($str, $from = 0, $to = FALSE) + { + if ($to !== FALSE) { + if ($from == $to || ($from <= 0 && $to < 0)) { + return ''; + } + + if ($from > $to) { + $from_copy = $from; + $from = $to; + $to = $from_copy; + } + } + + if ($from < 0) { + $from = 0; + } + + $substring = ($to === FALSE) ? substr($str, $from) : substr($str, $from, $to - $from); + return ($substring === FALSE) ? '' : $substring; + } + + + /** + * PHP port of Javascript's "slice" function for strings only + * Author: Tubal Martin http://blog.margenn.com + * Tests: http://margenn.com/tubal/str_slice/ + * + * @param string $str + * @param int $start index + * @param int|bool $end index (optional) + * @return string + */ + private function str_slice($str, $start = 0, $end = FALSE) + { + if ($end !== FALSE && ($start < 0 || $end <= 0)) { + $max = strlen($str); + + if ($start < 0) { + if (($start = $max + $start) < 0) { + return ''; + } + } + + if ($end < 0) { + if (($end = $max + $end) < 0) { + return ''; + } + } + + if ($end <= $start) { + return ''; + } + } + + $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start); + return ($slice === FALSE) ? '' : $slice; + } + + /** + * Convert strings like "64M" to int values + * @param mixed $size + * @return int + */ + private function normalizeInt($size) + { + if (is_string($size)) { + switch (substr($size, -1)) { + case 'M': case 'm': return (int)$size * 1048576; + case 'K': case 'k': return (int)$size * 1024; + case 'G': case 'g': return (int)$size * 1073741824; + default: return (int) $size; + } + } + return (int) $size; + } +} \ No newline at end of file diff --git a/sites/all/modules/advagg/advagg_css_compress/yui/Compressor.inc b/sites/all/modules/advagg/advagg_css_compress/yui/Compressor.inc new file mode 100644 index 0000000..c6cdd8b --- /dev/null +++ b/sites/all/modules/advagg/advagg_css_compress/yui/Compressor.inc @@ -0,0 +1,249 @@ + + * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) + */ +class Minify_CSS_Compressor { + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options (currently ignored) + * + * @return string + */ + public static function process($css, $options = array()) + { + $obj = new Minify_CSS_Compressor($options); + return $obj->_process($css); + } + + /** + * @var array + */ + protected $_options = null; + + /** + * Are we "in" a hack? I.e. are some browsers targetted until the next comment? + * + * @var bool + */ + protected $_inHack = false; + + + /** + * Constructor + * + * @param array $options (currently ignored) + */ + private function __construct($options) { + $this->_options = $options; + } + + /** + * Minify a CSS string + * + * @param string $css + * + * @return string + */ + protected function _process($css) + { + $css = str_replace("\r\n", "\n", $css); + + // preserve empty comment after '>' + // http://www.webdevout.net/css-hacks#in_css-selectors + $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css); + + // preserve empty comment between property and value + // http://css-discuss.incutio.com/?page=BoxModelHack + $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css); + $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css); + + // apply callback to all valid comments (and strip out surrounding ws + $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@' + ,array($this, '_commentCB'), $css); + + // remove ws around { } and last semicolon in declaration block + $css = preg_replace('/\\s*{\\s*/', '{', $css); + $css = preg_replace('/;?\\s*}\\s*/', '}', $css); + + // remove ws surrounding semicolons + $css = preg_replace('/\\s*;\\s*/', ';', $css); + + // remove ws around urls + $css = preg_replace('/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis) + \\s* + \\) # ) + /x', 'url($1)', $css); + + // remove ws between rules and colons + $css = preg_replace('/ + \\s* + ([{;]) # 1 = beginning of block or rule separator + \\s* + ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter) + \\s* + : + \\s* + (\\b|[#\'"-]) # 3 = first character of a value + /x', '$1$2:$3', $css); + + // remove ws in selectors + $css = preg_replace_callback('/ + (?: # non-capture + \\s* + [^~>+,\\s]+ # selector part + \\s* + [,>+~] # combinators + )+ + \\s* + [^~>+,\\s]+ # selector part + { # open declaration block + /x' + ,array($this, '_selectorsCB'), $css); + + // minimize hex colors + $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i' + , '$1#$2$3$4$5', $css); + + // remove spaces between font families + $css = preg_replace_callback('/font-family:([^;}]+)([;}])/' + ,array($this, '_fontFamilyCB'), $css); + + $css = preg_replace('/@import\\s+url/', '@import url', $css); + + // replace any ws involving newlines with a single newline + $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css); + + // separate common descendent selectors w/ newlines (to limit line lengths) + $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css); + + // Use newline after 1st numeric value (to limit line lengths). + $css = preg_replace('/ + ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value + \\s+ + /x' + ,"$1\n", $css); + + // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/ + $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css); + + return trim($css); + } + + /** + * Replace what looks like a set of selectors + * + * @param array $m regex matches + * + * @return string + */ + protected function _selectorsCB($m) + { + // remove ws around the combinators + return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]); + } + + /** + * Process a comment and return a replacement + * + * @param array $m regex matches + * + * @return string + */ + protected function _commentCB($m) + { + $hasSurroundingWs = (trim($m[0]) !== $m[1]); + $m = $m[1]; + // $m is the comment content w/o the surrounding tokens, + // but the return value will replace the entire comment. + if ($m === 'keep') { + return '/**/'; + } + if ($m === '" "') { + // component of http://tantek.com/CSS/Examples/midpass.html + return '/*" "*/'; + } + if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) { + // component of http://tantek.com/CSS/Examples/midpass.html + return '/*";}}/* */'; + } + if ($this->_inHack) { + // inversion: feeding only to one browser + if (preg_match('@ + ^/ # comment started like /*/ + \\s* + (\\S[\\s\\S]+?) # has at least some non-ws content + \\s* + /\\* # ends like /*/ or /**/ + @x', $m, $n)) { + // end hack mode after this comment, but preserve the hack and comment content + $this->_inHack = false; + return "/*/{$n[1]}/**/"; + } + } + if (substr($m, -1) === '\\') { // comment ends like \*/ + // begin hack mode and preserve hack + $this->_inHack = true; + return '/*\\*/'; + } + if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */ + // begin hack mode and preserve hack + $this->_inHack = true; + return '/*/*/'; + } + if ($this->_inHack) { + // a regular comment ends hack mode but should be preserved + $this->_inHack = false; + return '/**/'; + } + // Issue 107: if there's any surrounding whitespace, it may be important, so + // replace the comment with a single space + return $hasSurroundingWs // remove all other comments + ? ' ' + : ''; + } + + /** + * Process a font-family listing and return a replacement + * + * @param array $m regex matches + * + * @return string + */ + protected function _fontFamilyCB($m) + { + // Issue 210: must not eliminate WS between words in unquoted families + $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $out = 'font-family:'; + while (null !== ($piece = array_shift($pieces))) { + if ($piece[0] !== '"' && $piece[0] !== "'") { + $piece = preg_replace('/\\s+/', ' ', $piece); + $piece = preg_replace('/\\s?,\\s?/', ',', $piece); + } + $out .= $piece; + } + return $out . $m[2]; + } +} diff --git a/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info b/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info new file mode 100644 index 0000000..0fcfdfb --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.info @@ -0,0 +1,12 @@ +name = AdvAgg CDN Javascript +description = Use a shared CDN for javascript libraries, Google Libraries API currently. +package = Advanced CSS/JS Aggregation +core = 6.x +dependencies[] = advagg + +; Information added by drupal.org packaging script on 2012-06-25 +version = "6.x-1.9" +core = "6.x" +project = "advagg" +datestamp = "1340665277" + diff --git a/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module b/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module new file mode 100644 index 0000000..bb0ae05 --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_cdn/advagg_js_cdn.module @@ -0,0 +1,146 @@ + $data) { + // Skip inline and setting js. + if (!$data || $type == 'setting' || $type == 'inline') { + continue; + } + + // Search and replace. + foreach ($data as $path => $info) { + // jquery.js + if ($cdn_jquery) { + if (isset($jquery_update_filepath) && ($path == $jquery_update_filepath . '/replace/jquery.min.js' || $path == $jquery_filepath . '/replace/jquery.js')) { + $info['preprocess'] = FALSE; + $javascript['external'][$schema . '://ajax.googleapis.com/ajax/libs/jquery/' . $jquery_update_version . '/jquery.min.js'] = $info; + unset($javascript[$type][$path]); + } + elseif ($path == $jquery_filepath) { + $info['preprocess'] = FALSE; + $javascript['external'][$schema . '://ajax.googleapis.com/ajax/libs/jquery/' . $jquery_version . '/jquery.min.js'] = $info; + unset($javascript[$type][$path]); + } + } + + // Replace all jquery_ui scripts (jquery.ui.*.js) with a single + // Google-hosted minified version containing all of them. + if ($cdn_jquery_ui) { + if ( isset($jquery_ui_path) + && isset($jquery_ui_version) + && strpos($path, $jquery_ui_path) === 0 + ) { + $info['preprocess'] = FALSE; + $javascript['external'][$schema . '://ajax.googleapis.com/ajax/libs/jqueryui/' . $jquery_ui_version . '/jquery-ui.min.js'] = $info; + unset($javascript[$type][$path]); + } + } + + // swfobject.js + if ($cdn_swfobject) { + if ( isset($swfobject_filepath) + && $path == $swfobject_filepath + ) { + $info['preprocess'] = FALSE; + $javascript['external'][$schema . '://ajax.googleapis.com/ajax/libs/swfobject/' . $swfobject_version . '/swfobject.js'] = $info; + unset($javascript[$type][$path]); + } + } + } + } +} + +/** + * Get the path for the jquery-ui.js file. + * + * @param string $file + * filename. + */ +function advagg_js_cdn_get_jquery_ui_path() { + $jquery_ui_path_const = 'JQUERY_UI_PATH'; + if (!defined($jquery_ui_path_const)) { + if (!function_exists('jquery_ui_get_path')) { + return FALSE; + } + $jquery_ui_path = jquery_ui_get_path(); + if ($jquery_ui_path === FALSE) { + return FALSE; + } + } + else { + $jquery_ui_path = constant($jquery_ui_path_const); + } + $jquery_ui_path .= '/ui'; + + return $jquery_ui_path; +} diff --git a/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.admin.inc b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.admin.inc new file mode 100644 index 0000000..3b1e638 --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.admin.inc @@ -0,0 +1,61 @@ + 'checkbox', + '#title' => t('Compress JS Files'), + '#default_value' => variable_get('advagg_js_compress_agg_files', ADVAGG_JS_COMPRESS_AGG_FILES), + ); + $form['advagg_js_compress_inline'] = array( + '#type' => 'checkbox', + '#title' => t('Compress Inline JS'), + '#default_value' => variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE), + ); + + $description = ''; + $options = array(0 => t('JSMin+')); + if (function_exists('jsmin')) { + $options[1] = t('JSMin'); + $description .= t('JSMin is the C complied version and is about 25 times faster. Recommend using it.'); + } + else { + $description .= t('You can use the much faster C version of JSMin by installing the JSMin PHP Extension on this server.', array('@php_jsmin' => 'http://www.ypass.net/software/php_jsmin/')); + } + $form['advagg_js_compressor'] = array( + '#type' => 'radios', + '#title' => t('Select the compression program to use'), + '#default_value' => variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR), + '#options' => $options, + '#description' => filter_xss($description), + ); + + $form['advagg_js_compress_packer_enable'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Packer'), + '#default_value' => variable_get('advagg_js_compress_packer_enable', ADVAGG_JS_COMPRESS_PACKER_ENABLE), + '#description' => t('If enabled the non gzip version of JS files will be compressed using the JS Packer. WARNING: This has a high chance of breaking your JS. Only Enable on production after testing the non gzipped version locally.'), + ); + + return system_settings_form($form); +} diff --git a/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.info b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.info new file mode 100644 index 0000000..1aae7ac --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.info @@ -0,0 +1,12 @@ +name = AdvAgg Compress Javascript +description = Compress Javascript with a 3rd party compressor, JSMin+ currently. +package = Advanced CSS/JS Aggregation +core = 6.x +dependencies[] = advagg + +; Information added by drupal.org packaging script on 2012-06-25 +version = "6.x-1.9" +core = "6.x" +project = "advagg" +datestamp = "1340665277" + diff --git a/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.install b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.install new file mode 100644 index 0000000..7d1f429 --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.install @@ -0,0 +1,197 @@ + $t('AdvAgg JS Compress - Callback'), + 'severity' => REQUIREMENT_WARNING, + 'value' => $t('The callback for testing if a JS file is compressible is not working.'), + 'description' => $t('As a result if jsmin+ encounters a file that it cannot compress, it will kill that PHP process.'), + ); + if ($advagg_js_compress_callback != FALSE) { + variable_set('advagg_js_compress_callback', FALSE); + } + } + else { + $requirements['advagg_js_compress_callback'] = array( + 'title' => $t('AdvAgg JS Compress - Callback'), + 'severity' => REQUIREMENT_OK, + 'value' => $t('The callback is working correctly.'), + ); + if ($advagg_js_compress_callback == FALSE) { + variable_set('advagg_js_compress_callback', TRUE); + } + } + + // Test the 'memory_limit' PHP configuration directive + $memory_limit = ini_get('memory_limit'); + $compressor = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); + + // If $memory_limit contains a value of -1, the PHP runtime + // doesn't impose a limit on memory used by PHP scripts + if ($compressor == 0 && $memory_limit && $memory_limit != -1 && parse_size($memory_limit) < parse_size('96M')) { + $requirements['advagg_js_compress_memory_limit'] = array( + 'title' => $t('AdvAgg JS Compress - Memory Limit'), + 'value' => $memory_limit, + 'severity' => REQUIREMENT_WARNING, + 'description' => $t('It is highly recommended that you set your PHP memory_limit at least 96M if you are going to use JSMin+.'), + ); + } + } + return $requirements; +} + +/** + * Check to see if the CSS/JS generator is working. + */ +function advagg_js_compress_check_callback() { + $filename = drupal_get_path('module', 'advagg_js_compress') . '/jquery.form.js'; + $files_to_test = array(); + $files_to_test[] = array( + 'md5' => md5($filename), + 'filename' => $filename, + ); + $compressible = advagg_js_compress_test_compression($files_to_test); + return $compressible; +} + +/** + * Implementation of hook_schema(). + */ +function advagg_js_compress_schema() { + $schema = array(); + + // Create cache tables. + $schema['cache_advagg_js_compress_inline'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_js_compress_inline']['description'] = t('Cache table for Advanced CSS/JS Aggregations JS Compress module. Used to keep inline versions of compressed JS.'); + $schema['cache_advagg_js_compress_file'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_advagg_js_compress_file']['description'] = t('Cache table for Advanced CSS/JS Aggregations JS Compress module. Used to keep the compressed JavaScript from the js files.'); + + return $schema; +} + +/** + * Update 6100 - Clear file cache. + */ +function advagg_js_compress_update_6100() { + $ret = array(); + + cache_clear_all('*', 'cache_advagg_files_data', TRUE); + $ret[] = array( + 'success' => TRUE, + 'query' => 'advagg files_data cache flushed.', + ); + + return $ret; +} + +/** + * Update 6101 - Create the cache_advagg_css_compress_inline cache table. + */ +function advagg_js_compress_update_6101() { + $ret = array(); + + // Create cache table. + $schema = advagg_js_compress_schema(); + db_create_table($ret, 'cache_advagg_js_compress_inline', $schema['cache_advagg_js_compress_inline']); + + return $ret; +} + +/** + * Update 6102 - Create the cache_advagg_css_compress_file cache table. + */ +function advagg_js_compress_update_6102() { + $ret = array(); + + // Create cache table. + $schema = advagg_js_compress_schema(); + db_create_table($ret, 'cache_advagg_js_compress_file', $schema['cache_advagg_js_compress_file']); + + return $ret; +} + +/** + * Update 6103 - Clear the cache_advagg_css_compress_file cache table. + */ +function advagg_js_compress_update_6103() { + $ret = array(); + + // Clear cache_advagg_js_compress_file cache. + cache_clear_all('*', 'cache_advagg_js_compress_file', TRUE); + $ret[] = array( + 'success' => TRUE, + 'query' => 'The cache_advagg_js_compress_file table has been cleared.', + ); + + return $ret; +} diff --git a/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.module b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.module new file mode 100644 index 0000000..6257606 --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_compress/advagg_js_compress.module @@ -0,0 +1,597 @@ + 'advagg_js_compress_test_file', + 'type' => MENU_CALLBACK, + 'access callback' => TRUE, + ); + $items['admin/settings/advagg/js-compress'] = array( + 'title' => 'JS Compression', + 'description' => 'Adjust JS Compression settings.', + 'page callback' => 'advagg_js_compress_admin_page', + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer site configuration'), + 'file path' => $file_path, + 'file' => 'advagg_js_compress.admin.inc', + 'weight' => 10, + ); + + return $items; +} + +/** + * Implement hook_init. + */ +function advagg_js_compress_init() { + global $conf; + + if (variable_get('advagg_js_compress_packer_enable', ADVAGG_JS_COMPRESS_PACKER_ENABLE)) { + $conf['advagg_file_save_function'] = 'advagg_js_compress_file_saver'; + } +} + +/** + * Implement hook_advagg_files_table. + */ +function advagg_js_compress_advagg_files_table($row, $checksum) { + // IF the file has changed, test it's compressibility. + if ($row['filetype'] = 'js' && $checksum != $row['checksum']) { + $files_to_test[] = array( + 'md5' => $row['filename_md5'], + 'filename' => $row['filename'], + ); + advagg_js_compress_test_compression($files_to_test); + } +} + +/** + * Implement hook_advagg_js_pre_alter. + */ +function advagg_js_compress_advagg_js_pre_alter(&$javascript, $preprocess_js, $public_downloads, $scope) { + if (module_exists('jquery_update')) { + return; + } + + foreach ($javascript as $type => $data) { + if (!$data) { + continue; + } + if ($type == 'setting' || $type == 'inline') { + continue; + } + foreach ($data as $path => $info) { + if ($path == 'misc/jquery.form.js') { + $new_path = drupal_get_path('module', 'advagg_js_compress') . '/jquery.form.js'; + $javascript[$type][$new_path] = $info; + unset($javascript[$type][$path]); + } + } + } +} + +/** + * Implement hook_advagg_js_alter. + */ +function advagg_js_compress_advagg_js_alter(&$contents, $files, $bundle_md5) { + if (!variable_get('advagg_js_compress_agg_files', ADVAGG_JS_COMPRESS_AGG_FILES)) { + return; + } + + advagg_js_compress_prep($contents, $files, $bundle_md5); +} + +/** + * Implement hook_advagg_js_inline_alter. + */ +function advagg_js_compress_advagg_js_inline_alter(&$contents) { + if (!variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE)) { + return; + } + $compressor = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); + + // If using a cache, try to get the contents of it. + if (variable_get('advagg_js_compress_inline_cache', ADVAGG_JS_COMPRESS_INLINE_CACHE)) { + $key = md5($contents) . $compressor; + $table = 'cache_advagg_js_compress_inline'; + $data = cache_get($key, $table); + if (!empty($data->data)) { + $contents = $data->data; + return; + } + } + + if ($compressor == 0) { + $original_contents = $contents; + + list($before, $after) = advagg_js_compress_jsminplus($contents); + $ratio = 0; + if ($before != 0) { + $ratio = ($before - $after) / $before; + } + // Make sure the returned string is not empty or has a VERY high + // compression ratio. + if (empty($contents) || empty($ratio) || $ratio > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { + $contents = $original_contents; + } + + } + if ($compressor == 1) { + $contents = jsmin($contents); + } + + // If using a cache set it. + if (isset($key)) { + cache_set($key, $contents, $table, CACHE_TEMPORARY); + } +} + +/** + * Compress a JS string + * + * @param $contents + * Javascript string. + */ +function advagg_js_compress_prep(&$contents, $files, $bundle_md5) { + // Make sure every file in this aggregate is compressible. + $files_to_test = array(); + $list_bad = array(); + foreach ($files as $filename) { + $filename_md5 = md5($filename); + $data = advagg_get_file_data($filename_md5); + + // File needs to be tested. + if (empty($data['advagg_js_compress']['tested'])) { + $files_to_test[] = array( + 'md5' => $filename_md5, + 'filename' => $filename, + ); + } + elseif ($data['advagg_js_compress']['tested']['jsminplus'] != 1) { + $list_bad[$filename] = $filename; + } + } + + $advagg_js_compress_callback = variable_get('advagg_js_compress_callback', ADVAGG_JS_COMPRESS_CALLBACK); + if ($advagg_js_compress_callback) { + // Send test files to worker. + if (!empty($files_to_test)) { + $compressible = advagg_js_compress_test_compression($files_to_test); + // If an array then it is a list of files that can not be compressed. + if (is_array($compressible)) { + // Place filename in an array key. + foreach ($compressible as $filedata) { + $filename = $filedata['filename']; + $list_bad[$filename] = $filename; + } + } + } + } + + $contents = ''; + // Do not compress the file that it bombs on. + // Compress each file individually. + foreach ($files as $file) { + if (!empty($list_bad[$file])) { + $contents .= advagg_build_js_bundle(array($file)); + } + else { + $data = advagg_build_js_bundle(array($file)); + + // If using a cache, try to get the contents of it. + $cached = FALSE; + if (variable_get('advagg_js_compress_file_cache', ADVAGG_JS_COMPRESS_FILE_CACHE)) { + $key = $file; + $table = 'cache_advagg_js_compress_file'; + $cached_data = cache_get($key, $table); + if (!empty($cached_data->data)) { + $data = $cached_data->data; + $cached = TRUE; + } + } + if (!$cached && !empty($data)) { + $compressor = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); + if ($compressor == 0) { + list($before, $after) = advagg_js_compress_jsminplus($data); + $ratio = 0; + if ($before != 0) { + $ratio = ($before - $after) / $before; + } + // Make sure the returned string is not empty or has a VERY high + // compression ratio. + if (empty($data) || empty($ratio) || $ratio > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { + $data = advagg_build_js_bundle(array($file)); + } + elseif (isset($key)) { + // If using a cache set it. + cache_set($key, $data, $table); + } + } + elseif ($compressor == 1) { + $contents = jsmin($contents); + } + } + $url = url($file, array('absolute' => TRUE)); + $contents .= "/* Source and licensing information for the line(s) below can be found at $url. */\n" . $data . ";\n/* Source and licensing information for the above line(s) can be found at $url. */\n"; + } + } +} + +/** + * Compress a JS string using jsmin+ + * + * @param $contents + * Javascript string. + * @return + * array with the size before and after. + */ +function advagg_js_compress_jsminplus(&$contents) { + // Try to allocate enough time to run JSMin+. + if (function_exists('set_time_limit')) { + @set_time_limit(240); + } + + // Only include jsminplus.inc if the JSMinPlus class doesn't exist. + if (!class_exists('JSMinPlus')) { + include(drupal_get_path('module', 'advagg_js_compress') . '/jsminplus.inc'); + } + // Get the JS string length before the compression operation. + $before = strlen($contents); + $original_contents = $contents; + try { + // Strip Byte Order Marks (BOM's) from the file, JSMin+ cannot parse these. + $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); + ob_start(); + // JSMin+ the contents of the aggregated file. + $contents = JSMinPlus::minify($contents); + $error = trim(ob_get_contents()); + if (!empty($error)) { + throw new Exception($error); + } + // Get the JS string length after the compression operation. + $after = strlen($contents); + } + catch (Exception $e) { + // Log the exception thrown by JSMin+ and roll back to uncompressed content. + watchdog('advagg', $e->getMessage() . '
' . $original_contents . '
', NULL, WATCHDOG_WARNING); + $contents = $original_contents; + $after = $before; + } + ob_end_clean(); + return array($before, $after); +} + +/** + * Run various theme functions so the cache is primed. + * + * @param $files_to_test + * array with md5 and filename. + * @return + * TRUE if all files are compressible. List of files that failed otherwise. + */ +function advagg_js_compress_test_compression($files_to_test) { + global $base_path; + $bad_files = array(); + + // Blacklist jquery.min.js from getting compressed. + if (module_exists('jquery_update')) { + foreach ($files_to_test as $key => $info) { + if (strpos($info['filename'], 'jquery.min.js') !== FALSE) { + // Add file to the bad list. + $bad_files[] = $info; + unset($files_to_test[$key]); + + // Get file data. + $filename_md5 = md5($info['filename']); + $lock_name = 'advagg_set_file_data_' . $filename_md5; + if (!lock_acquire($lock_name, 10)) { + lock_wait($lock_name); + continue; + } + $data = advagg_get_file_data($filename_md5); + + // Set to -2 + if (!isset($data->data['advagg_js_compress']['tested']['jsminplus']) || $data->data['advagg_js_compress']['tested']['jsminplus'] != -2) { + $data['advagg_js_compress']['tested']['jsminplus'] = -2; + advagg_set_file_data($filename_md5, $data); + } + lock_release($lock_name); + } + } + } + + foreach ($files_to_test as $info) { + $key = variable_get('advagg_js_compress_url_key', FALSE); + if (empty($key)) { + $key = mt_rand(); + variable_set('advagg_js_compress_url_key', $key); + } + + // Clear the cache for this file + cache_clear_all($info['filename'], 'cache_advagg_js_compress_file'); + + // Setup request URL and headers. + $query['values'] = $info; + $query['key'] = $key; + $query_string = http_build_query($query, '', '&'); + $url = _advagg_build_url('advagg/js_compress_test_file'); + $headers = array( + 'Host' => $_SERVER['HTTP_HOST'], + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Connection' => 'close', + ); + + $results = drupal_http_request($url, $headers, 'POST', $query_string); + + // Get file data. + $filename_md5 = md5($info['filename']); + $data = advagg_get_file_data($filename_md5); + + // Mark as a bad file. + if (empty($data['advagg_js_compress']['tested']['jsminplus']) || $data['advagg_js_compress']['tested']['jsminplus'] != 1) { + $bad_files[] = $info; + } + } + if (empty($bad_files)) { + return TRUE; + } + return $bad_files; +} + +/** + * Run various theme functions so the cache is primed. + * + * @param $values + * object File info + */ +function advagg_js_compress_test_file($values = NULL) { +// watchdog('debug', str_replace(' ', '    ', nl2br(htmlentities(print_r($values, TRUE) . print_r($_REQUEST, TRUE))))); + + // Exit if key does not match & called with $file not set. + if (is_null($values)) { + if (empty($_POST['key']) || empty($_POST['values'])) { + return; + } + $key = variable_get('advagg_js_compress_url_key', FALSE); + if ($key != $_POST['key']) { + return; + } + $values = array(); + $values['values'] = $_POST['values']; + } + $filename = $values['values']['filename']; + $md5 = $values['values']['md5']; + + // Compression test file if it exists. + advagg_clearstatcache(TRUE, $filename); + if (file_exists($filename)) { + $contents = file_get_contents($filename); + $filesize = filesize($filename); + + $lock_name = 'advagg_set_file_data_' . $md5; + if (!lock_acquire($lock_name, 45)) { + lock_wait($lock_name); + echo $md5; + exit; + } + $data = advagg_get_file_data($md5); + + // Set to "-1" so if php bombs out, the file will be marked as bad. + $data['advagg_js_compress']['tested']['jsminplus'] = -1; + advagg_set_file_data($md5, $data); + + // Compress the data. + list($before, $after) = advagg_js_compress_jsminplus($contents); + + // Set to "-2" if compression ratio sucks. + $ratio = 0; + if ($before != 0) { + $ratio = ($before - $after) / $before; + } + if ($ratio < variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO)) { + $data['advagg_js_compress']['tested']['jsminplus'] = -2; + advagg_set_file_data($md5, $data); + lock_release($lock_name); + echo $md5; + exit; + } + // Set to "-3" if the compression ratio is way too good. + if ($ratio > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { + $data['advagg_js_compress']['tested']['jsminplus'] = -3; + advagg_set_file_data($md5, $data); + lock_release($lock_name); + echo $md5; + exit; + } + + // Everything worked, mark this file as compressible. + $data['advagg_js_compress']['tested']['jsminplus'] = 1; + advagg_set_file_data($md5, $data); + + // Set the file cache. + if (variable_get('advagg_js_compress_file_cache', ADVAGG_JS_COMPRESS_FILE_CACHE)) { + $key = $filename; + $table = 'cache_advagg_js_compress_file'; + cache_set($key, $contents, $table); + } + } + + if (isset($lock_name)) { + lock_release($lock_name); + } + echo $md5; + exit; +} + +/** + * Save a string to the specified destination. Verify that file size is not zero. + * + * @param $data + * A string containing the contents of the file. + * @param $dest + * A string containing the destination location. + * @return + * Boolean indicating if the file save was successful. + */ +function advagg_js_compress_file_saver($data, $dest, $force, $type) { + if ($type == 'css') { + return advagg_file_saver($data, $dest, $force, $type); + } + if (!variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION) || !extension_loaded('zlib')) { + return advagg_file_saver($data, $dest, $force, $type); + } + + // Get file save function + $file_save_data = 'file_save_data'; + $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); + if (!empty($custom_path)) { + $file_save_data = 'advagg_file_save_data'; + } + + // Gzip first. + $gzip_dest = $dest . '.gz'; + advagg_clearstatcache(TRUE, $gzip_dest); + if (!file_exists($gzip_dest) || $force) { + $gzip_data = gzencode($data, 9, FORCE_GZIP); + if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + + // Make sure filesize is not zero. + advagg_clearstatcache(TRUE, $gzip_dest); + if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) { + if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + advagg_clearstatcache(TRUE, $gzip_dest); + if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) { + // Filename is bad, create a new one next time. + file_delete($gzip_dest); + return FALSE; + } + } + } + + // Use packer on JS data. + advagg_js_compress_jspacker($data); + + // Write File. + if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + + // Make sure filesize is not zero. + advagg_clearstatcache(TRUE, $dest); + if (@filesize($dest) == 0 && !empty($data)) { + if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) { + return FALSE; + } + advagg_clearstatcache(TRUE, $dest); + if (@filesize($dest) == 0 && !empty($data)) { + // Filename is bad, create a new one next time. + file_delete($dest); + return FALSE; + } + } + + // Make sure .htaccess file exists. + advagg_htaccess_check_generate($dest); + + cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT); + return TRUE; +} + +/** + * Compress a JS string using packer. + * + * @param $contents + * Javascript string. + */ +function advagg_js_compress_jspacker(&$contents) { + // Use Packer on the contents of the aggregated file. + require_once(drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'); + + // Add semicolons to the end of lines if missing. + $contents = str_replace("}\n", "};\n", $contents); + $contents = str_replace("\nfunction", ";\nfunction", $contents); + + // Remove char returns, looking at you lightbox2. + $contents = str_replace("\n\r", "", $contents); + $contents = str_replace("\r", "", $contents); + $contents = str_replace("\n", "", $contents); + + $packer = new JavaScriptPacker($contents, 62, TRUE, FALSE); + $contents = $packer->pack(); +} + +/** + * Implementation of hook_flush_caches(). + */ +function advagg_js_compress_flush_caches() { + return array('cache_advagg_js_compress_inline'); +} + +/** + * Implementation of hook_advagg_master_reset(). + */ +function advagg_js_compress_advagg_master_reset() { + cache_clear_all('*', 'cache_advagg_js_compress_inline', TRUE); + cache_clear_all('*', 'cache_advagg_js_compress_file', TRUE); +} diff --git a/sites/all/modules/advagg/advagg_js_compress/jquery.form.js b/sites/all/modules/advagg/advagg_js_compress/jquery.form.js new file mode 100644 index 0000000..f484069 --- /dev/null +++ b/sites/all/modules/advagg/advagg_js_compress/jquery.form.js @@ -0,0 +1,660 @@ +/* + * jQuery Form Plugin + * version: 2.36 (07-NOV-2009) + * @requires jQuery v1.2.6 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function() { + $(this).ajaxSubmit({ + target: '#output' + }); + return false; // <-- important! + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') + options = { success: options }; + + var url = $.trim(this.attr('action')); + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + url = url || window.location.href || ''; + + options = $.extend({ + url: url, + type: this.attr('method') || 'GET', + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options || {}); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (var n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) + a.push( { name: n, value: options.data[n][k] } ); + } + else + a.push( { name: n, value: options.data[n] } ); + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + $(options.target).html(data).each(oldSuccess, arguments); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i].apply(options, [data, status, $form]); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + var multipart = false; +// var mp = 'multipart/form-data'; +// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if ((files.length && options.iframe !== false) || options.iframe || found || multipart) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) + $.get(options.closeKeepAlive, fileUpload); + else + fileUpload(); + } + else + $.ajax(options); + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + if ($(':input[name=submit]', form).length) { + alert(Drupal.t('Error: Form elements must not be named "submit".')); + return; + } + + var opts = $.extend({}, $.ajaxSettings, options); + var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); + + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $('