first import
This commit is contained in:
339
sites/all/modules/core_library/LICENSE.txt
Normal file
339
sites/all/modules/core_library/LICENSE.txt
Normal file
@@ -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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 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.
|
219
sites/all/modules/core_library/README.txt
Normal file
219
sites/all/modules/core_library/README.txt
Normal file
@@ -0,0 +1,219 @@
|
||||
Core Library
|
||||
============
|
||||
|
||||
This module changes the Drupal core aggregation mecanism process. It greatly
|
||||
reduces I/O and aggregated number of files, and improves chances of client
|
||||
cache hit, therefore while it produces bigger aggregated files, it reduces
|
||||
greatly the bandwidth utilization while users browse.
|
||||
|
||||
This is achieved by bypassing the dynamic CSS and JS inclusion. Instead of
|
||||
including and aggregating only needed files on a per-page basis, it learns
|
||||
files being used site-wide while user browse, then is able to produce larger
|
||||
therefore more revelant files that aggregate all those atomic files whether
|
||||
or not they are being used on the page.
|
||||
|
||||
Over time, the number of aggregated files reduce to achieve a stable state
|
||||
where all site JS files are being aggregated in one file only, and all site
|
||||
CSS are being aggretated in only two files (libraries into one side, theme
|
||||
files into another, can be more if there are browser-specific files).
|
||||
|
||||
Once the files are stable, site administrator can then enable the full bypass
|
||||
mode, which will only uses the actual saved state and won't ever do any more
|
||||
I/O for CSS and JS inclusion, based on the cached state.
|
||||
|
||||
On production sites that do not changes nor update their module often, the
|
||||
performance boost is significant because no dynamic inclusion is being done
|
||||
anymore (therefore no I/O are made at all), as the bandwidth consumption is
|
||||
greatly reduced (because clients will cache these files at first hit and won't
|
||||
download them on the server after that).
|
||||
|
||||
This module produces really a few side-effects, depeing on the theme coding
|
||||
mainly. We bypass core mecanism, but keep sanefully files weighting which will
|
||||
avoid most potential conflicts.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
||||
Drupal 7 does an hard and heavy work about JS and CSS aggregation. It is able
|
||||
to separate aggregated files into multiple groups. This is a good thing for
|
||||
lazzy site admins, the first reason is by doing finer groups, those per-group
|
||||
aggregated files will have greater chances of getting static file hits.
|
||||
|
||||
Therefore, there is a main disadvantage: because groups are hardcoded, even on
|
||||
a site where the files does not change on a per-page basis, the site will ever
|
||||
aggregate at least 3 JS files, and 3 CSS files, which is not quite elegant.
|
||||
|
||||
On larger scale sites, site admin would want to bypass this ugly aggregation
|
||||
style which would cause something like 4 to even a lot more useless HTTP
|
||||
requests, on almost each client hit because aggregated files will be differnt
|
||||
on a per-page basis.
|
||||
|
||||
The ideal case would be to have only one sitewide JS file, as only one sitewide
|
||||
CSS file. This won't happen because:
|
||||
- JS are split between libraries and Drupal locale translations.
|
||||
- CSS are split between libraries and theme files (we do that, for a good
|
||||
reason).
|
||||
So, our ideal case will be to have only two sitewide files of each.
|
||||
|
||||
Original goal and methodology
|
||||
-----------------------------
|
||||
|
||||
This module intend to allow site administrators to manually set which files
|
||||
among all core libraries and module specific files should be aggregated as
|
||||
core immutable libraries.
|
||||
|
||||
This is not true anymore because of the learning mecanism, which will be
|
||||
enabled per default. Therefore, the suicidal tendencies of site admins tell
|
||||
me that some of them will still use the manual UI in order to build their own
|
||||
aggregation rules. In fact, the module has been built for this, and this is
|
||||
a totally legal, moreover totally legitimate thing to do.
|
||||
|
||||
When using this manual mode, each one of the selected JS file candidate for
|
||||
sitewide aggregation will be considered as a library and will be forced to
|
||||
get to the JS_LIBRARY group. by doing this we open the door to file weight
|
||||
conflict between group, thus, we have an override mecanism that will consider
|
||||
each group as a weight addition and will pragmatically add an indescent weight
|
||||
factor to files that core does not expose itself as a library. This will help
|
||||
keeping things in order.
|
||||
|
||||
JS minification HOWTO
|
||||
---------------------
|
||||
|
||||
This module can use the JSMin library PHP port in order to minify JS files.
|
||||
|
||||
Right now, we won't use any other contrib module in order to remain dependency
|
||||
agnostic. This is important because this module has one and only one simple
|
||||
goal, and it shouldn't rely on any other module that add extensive execution
|
||||
overhead.
|
||||
|
||||
All is about performances, and also leaving the choice for the site admin to
|
||||
do what he want to do.
|
||||
|
||||
So, we don't use any library handling module, sorry, it may change, but right
|
||||
now the only good way of making it work is by adding the jsmin.php file into
|
||||
this folder:
|
||||
sites/all/libraries/jsmin/
|
||||
|
||||
You can download it there:
|
||||
https://github.com/rgrove/jsmin-php/
|
||||
|
||||
Once you downloaded it, you can enable minification on the Core Library module
|
||||
configuration page, and the form won't revert the option automatically anymore.
|
||||
|
||||
We do not provide this library because its licence may conflict with the GPL
|
||||
one, sorry about that, but you are on your own for downloading it.
|
||||
|
||||
Potential known side effect with CSS
|
||||
------------------------------------
|
||||
|
||||
CSS files are not processed the same way, because administration screens and
|
||||
frontend pages won't have the same theme, we can't merge groups else we would
|
||||
totally break the aggregation benefit of having only one large file. Solution
|
||||
is a bit ugly, but will work on most sites: we pragmatically override the
|
||||
CSS_DEFAULT group and add module specific CSS into the CSS_SYSTEM group, using
|
||||
the same weight alteration mecanism as we use for JS.
|
||||
|
||||
The side effect of this (there is always one) is that Drupal, per default, will
|
||||
put CSS_DEFAULT files after CSS_THEME theme specific files. We reverse order
|
||||
and set the CSS_DEFAULT into the CSS_SYSTEM which break this behavior. This can
|
||||
lead to CSS directive conflicts for theme that mess up with module specific CSS
|
||||
files. Because of this statement, the CSS group merge remains optional.
|
||||
|
||||
But it seems we are lucky! Because theme CSS files are ordered before the module
|
||||
specifics, it happens that theme developers are forced to do proper CSS override
|
||||
using CSS directive specialization instead of relying onto order, which make our
|
||||
reverse algorithm to almost always work as-is with well coded themes.
|
||||
|
||||
Auto-configuration
|
||||
------------------
|
||||
|
||||
This module provide a learning mode. Three different profiles are provided for
|
||||
this learning mode, they are described in administration section. These modes
|
||||
all works quite the same:
|
||||
|
||||
1. At JS or CSS alteration time, unknown files that should be displayed
|
||||
on page are found.
|
||||
|
||||
2. Once all these files have been found, the manual variable is populated with
|
||||
the new files, and saved.
|
||||
|
||||
3. Then, the alteration goes, also using new file found for override, thus
|
||||
aggregating them the first time.
|
||||
|
||||
4. When a new page is hit, chances that a new file is found is naturally
|
||||
lowered, the more users browse, the faster you'll reach a state where your
|
||||
Drupal site naturally aggregates all the files in one and only one file.
|
||||
|
||||
Once the configuration actually fits you (you have to do some profiling on your
|
||||
own for that) you can then switch back to manual or bypass mode in order for
|
||||
the learning process to stop. The module will still continue to force file
|
||||
aggregation, using the earlier learnt files.
|
||||
|
||||
You can always go back, enable the UI module again and customize the automated
|
||||
configuration on your own then.
|
||||
|
||||
Bypass mode
|
||||
-----------
|
||||
|
||||
When the configuration is OK, site admin should switch to full bypass mode.
|
||||
This particular mode alter the 'styles' and 'scripts' element types element
|
||||
info, and forces it to be rendered with our own functions.
|
||||
|
||||
When creating CSS aggregated files using the bypass mode, the CSS grouping
|
||||
mode will gracefully be set to 'dual' mode. This ensures that in case one
|
||||
or more themes are enabled, there will always be only one included in the
|
||||
page and will avoid CSS conflicts between themes.
|
||||
|
||||
This also ensures that each theme will have its own aggregated CSS file that
|
||||
won't never be modified anymore until the admin switches back to another mode.
|
||||
|
||||
Once the bypass mode is set, aggregated files are generated only once using
|
||||
the current learnt files state. Those files, once aggregated, won't regenerate
|
||||
themselves if the files are being manually deleted on the system, so if it
|
||||
happens, an specific button on the administration page will allow you to force
|
||||
the module to rebuild these.
|
||||
|
||||
Bypass mode and drupal_add_TYPE()
|
||||
---------------------------------
|
||||
|
||||
When using bypass mode, the hook_library_TYPE_alter() hooks will still be used
|
||||
during page construction, this ensures the files' weights to be the right one,
|
||||
we don't store the weight and we won't.
|
||||
|
||||
These functions may still do some file_exists() I/O that we should definitely
|
||||
get rid off. This will be a future challenge.
|
||||
|
||||
Because drupal_add_TYPE() function will still be run whatever we do (we can't
|
||||
take over module's code dynamically), why not use their result anyway?
|
||||
|
||||
UI and stat collection systems (Core Library Advanced UI module)
|
||||
----------------------------------------------------------------
|
||||
|
||||
When in manual mode, aggregation rules can be built over administrative file
|
||||
listings. We can get a list of system known libraries quite easily and expose
|
||||
it to the site admin.
|
||||
|
||||
Nevertheless we have problems with modules' arbitrary set files, that the
|
||||
system itself can't guess before those files are being processed at least
|
||||
once.
|
||||
|
||||
We put in place a statistics collection system (which has really heavy and
|
||||
indencent performance impact) that allows developers to play with a development
|
||||
site and let the system discover files while browsing. Once all files (the only
|
||||
thing the developer can do is hope that all files have been processed at least
|
||||
once) have been discovered, another configuration screen is populated with what
|
||||
we call the 'orphan files'. Those files are displayed side by side with number
|
||||
of hits for each one of them.
|
||||
|
||||
This 'orphan' screen can then help decide which files the site admin would want
|
||||
to force being processed and aggregated site wide.
|
||||
|
||||
Future
|
||||
------
|
||||
|
||||
We intend to add a JS minification mecanism to enfore files to get through more
|
||||
than aggregation, but also minification. This will save up a bit of bandwidth.
|
||||
|
||||
Some badly coded JS files won't pass through the minification and will cause
|
||||
errors on client side, so we will make this mecanism site wide optional, at
|
||||
first, then per file optional then.
|
146
sites/all/modules/core_library/core_library.admin.inc
Normal file
146
sites/all/modules/core_library/core_library.admin.inc
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library module administration pages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main administration screen.
|
||||
*/
|
||||
function core_library_admin_settings($form, &$form_state) {
|
||||
|
||||
$form['bypass'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t("Drupal aggregation mecanism bypass"),
|
||||
'#collapsible' => FALSE,
|
||||
);
|
||||
$form['bypass']['core_library_bypass'] = array(
|
||||
'#type' => 'radios',
|
||||
'#options' => array(
|
||||
NULL => t("Disabled") . '<br/><small>' . t("Let Drupal core do what it has to do. If you select this, you should then disable this module.") . '</small>',
|
||||
LIBRARY_MODE_MANUAL => t("Manual") . '<br/><small>' . t("Let the site admin choose which file should go through the aggregation process. This needs the Core Library Advanced UI module to be enabled at least to choose the files.") . '</small>',
|
||||
LIBRARY_MODE_LEARNING_ANONYMOUS => t("Anonymous only") . '<br/><small>' . t("Automatically learn files that are being used for anymous users only.") . '</small>',
|
||||
LIBRARY_MODE_LEARNING_NO_ADMIN => t("All, no admin") . '<br/><small>' . t("Automatically learn files to aggregate while users browse, excluding the administration pages.") . '</small>',
|
||||
LIBRARY_MODE_LEARNING_ALL => t("All <cite>Pinage!</cite>") . '<br/><small>' . t("Automatically learn files to aggregate while users browse, including administration pages.") . '</small>',
|
||||
LIBRARY_MODE_BYPASS => t("Full bypass <cite>I'm done playing!</cite>") . '<br/><small>' . t("As you select this option, you consider that auto learning did its job, and you don't want to let Drupal core mess up anymore with aggregated files. This means that it won't never ever see new CSS and JS files until you revert this option, but you will save a lot of I/O bypassing its aggregation mecanism, good for production sites, totally wrong for development or testing sites.") . '</small>',
|
||||
),
|
||||
'#default_value' => variable_get('core_library_bypass', NULL),
|
||||
);
|
||||
|
||||
$form['css'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t("CSS grouping mode"),
|
||||
'#collapsible' => FALSE,
|
||||
);
|
||||
$form['css']['core_library_css_group'] = array(
|
||||
'#type' => 'radios',
|
||||
'#options' => array(
|
||||
NULL => t("Disabled") . '<br/><small>' . t("Let Drupal core group CSS as it intend to. This means that you will always get (at least) 3 CSS files, one per group.") . '</small>',
|
||||
LIBRARY_CSS_GROUP_DUAL => t("Dual mode") . '<br/><small>' . t("System libraries, and arbitrary module files in one single file, followed by theme specifics into another file.") . '</small>',
|
||||
),
|
||||
'#default_value' => variable_get('core_library_css_group', NULL),
|
||||
);
|
||||
|
||||
$form['behavior'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t("Behavior options"),
|
||||
'#collapsible' => FALSE,
|
||||
);
|
||||
$form['behavior']['core_library_respectful_preprocess'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t("Respect modules preprocess settings"),
|
||||
'#default_value' => variable_get('core_library_respectful_preprocess', TRUE),
|
||||
'#description' => t("Modules can decide weither or not their JavaScript or CSS files should preprocessed or not. Some of them are complex video players, such as the Media Element script and need multiple files packaged with them. When a JavaScript is preprocessed, it is moved to another location which can prevent it from finding additional files, if you experience JavaScript problems try to enable this option."),
|
||||
);
|
||||
|
||||
$form['advanced'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t("Advanced options"),
|
||||
'#collapsible' => FALSE,
|
||||
);
|
||||
$form['advanced']['core_library_js_minify'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t("Minify JS"),
|
||||
'#default_value' => variable_get('core_library_js_minify', FALSE),
|
||||
'#description' => t("If checked, the system will attempt to minify JS files. This can lead to some JS crashing since a lot of custom JS files may not have been well formatted. Browsers are tolerant while minifier is not on JS syntax."),
|
||||
);
|
||||
|
||||
$form = system_settings_form($form);
|
||||
|
||||
$form['actions']['submit']['#submit'] = $form['#submit'];
|
||||
unset($form['#submit']);
|
||||
$form['actions']['submit']['#submit'][] = 'core_library_admin_settings_submit_prepare_cache';
|
||||
|
||||
$form['actions']['clear'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t("Reset learnt files"),
|
||||
'#submit' => array('core_library_admin_settings_submit_clear'),
|
||||
);
|
||||
$form['actions']['rebuild'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t("Rebuild files"),
|
||||
'#attributes' => array('title' => t("This will force the system to rebuild aggregated and minified files.")),
|
||||
'#submit' => array('core_library_admin_settings_submit_rebuild'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare cache submit handler.
|
||||
*/
|
||||
function core_library_admin_settings_submit_prepare_cache($form, &$form_state) {
|
||||
$error = NULL;
|
||||
$error_variables = array();
|
||||
$directory = 'public://static';
|
||||
|
||||
// Check our final static folder is valid.
|
||||
if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
$error = "Failed to create 'static-aggregation' directory, reverting aggregation bypass mode to manual.";
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
// Error must not break the Drupal site. In case of any error, revert to
|
||||
// manual mode, so the module will continue to work, but Drupal will also
|
||||
// continue to built its own final html head rendering.
|
||||
watchdog('core_library', $error, $error_variables, WATCHDOG_ERROR);
|
||||
drupal_set_message(t($error, $error_variables), 'error');
|
||||
|
||||
// Revert variable, and set the error message for the end user.
|
||||
variable_set('core_library_bypass', LIBRARY_MODE_MANUAL);
|
||||
}
|
||||
|
||||
// Also check for the JSMin library to be present, if the option is checked.
|
||||
if (variable_get('core_library_js_minify', TRUE) && !file_exists(core_library_js_minify_library_path())) {
|
||||
drupal_set_message(t("JSMin library was not found, see README.txt"), 'error');
|
||||
variable_set('core_library_js_minify', FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear learnt files submit handler.
|
||||
*/
|
||||
function core_library_admin_settings_submit_clear($form, &$form_state) {
|
||||
variable_del('library_aggregation_settings');
|
||||
variable_del('library_aggregation_orphans');
|
||||
variable_del('core_library_css_status');
|
||||
variable_del('core_library_updated');
|
||||
drupal_set_message(t("Current aggregation settings have been removed."));
|
||||
|
||||
$current_mode = variable_get('core_library_bypass', NULL);
|
||||
if ($current_mode == LIBRARY_MODE_BYPASS || $current_mode == LIBRARY_MODE_MANUAL) {
|
||||
variable_set('core_library_bypass', NULL);
|
||||
drupal_set_message(t("Aggregation mode was bypass or manual, set back to default."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild aggregated files submit handler.
|
||||
*/
|
||||
function core_library_admin_settings_submit_rebuild($form, &$form_state) {
|
||||
variable_del('core_library_css_status');
|
||||
variable_del('core_library_js_minified');
|
||||
drupal_set_message(t("Aggregated files marked for rebuild."));
|
||||
drupal_set_message(t("Minified files cache destroyed."));
|
||||
}
|
183
sites/all/modules/core_library/core_library.bypass.inc
Normal file
183
sites/all/modules/core_library/core_library.bypass.inc
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library bypass mode specific functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rebuild files for the given CSS group.
|
||||
*
|
||||
* This function is mainly a proxy to drupal_build_css_cache() which does its
|
||||
* job quite well.
|
||||
*
|
||||
* Because this function will name files using an ugly hash and does heavy I/O,
|
||||
* ours will call it only if really necessary, and will use non-randomized file
|
||||
* names for HTML inclusion, therefore will proceed to a file copy right after
|
||||
* each aggregated file has been created.
|
||||
*
|
||||
* @param array $map
|
||||
* Mapped filename.
|
||||
* @param array $medias
|
||||
* Key value pairs, keys are CSS media property, values are Drupal core CSS
|
||||
* description array, weight ordered, suitable for building the aggregated
|
||||
* file.
|
||||
*
|
||||
* @return mixed
|
||||
* Suitable value for stored status array. If there was any during aggregated
|
||||
* file creation or copy, this will break any remaining files to aggregate
|
||||
* return NULL, this will force a new attempt on next page hits.
|
||||
*
|
||||
* @see core_library_element_style_pre_render()
|
||||
* @see drupal_build_css_cache()
|
||||
*/
|
||||
function _core_library_element_style_rebuild_group($map, $css) {
|
||||
// Best case scenario, we have no files.
|
||||
if (empty($css)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Build the file using core function.
|
||||
if ($file_uri = drupal_build_css_cache($css)) {
|
||||
if (file_unmanaged_copy($file_uri, 'public://static/' . $map . '.css', FILE_EXISTS_REPLACE)) {
|
||||
// Mark the file as built.
|
||||
watchdog('core_library', "CSS file for mapping " . $map . " has been sucessfully built", NULL, WATCHDOG_NOTICE);
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
watchdog('core_library', "CSS file for mapping " . $map . " could not be copied after being built", NULL, WATCHDOG_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Built has failed, therefore we cannot continue.
|
||||
watchdog('core_library', "CSS file for mapping " . $map . " could not be built", NULL, WATCHDOG_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drupal_pre_render_styles() replacement.
|
||||
*
|
||||
* This function pass through multiple steps:
|
||||
* 1. Fetch the current aggregated files status in the $status array.
|
||||
* 2. Fetch the user set groups configuration in the $groups array.
|
||||
* 3. Check we have files matching in the status array for each group, mark
|
||||
* each missing file for rebuild in the $rebuild array.
|
||||
* 4. Iterate over the $rebuild array, and rebuild missing files (if they are
|
||||
* files matching the group).
|
||||
* 5. If any modification has been done to $status, save it for next hits.
|
||||
* 6. Build the drupal_render() final result using the current $status array
|
||||
* which has been modified or not.
|
||||
* 7. Remove items we consumed from the final list, and let external files
|
||||
* being processed by core.
|
||||
*
|
||||
* During the whole process, we use a $mapping array. This array contain, for
|
||||
* each group, an alphanumerical filename prefix for future aggregated files.
|
||||
*
|
||||
* Filenames are being built using this mapping array and this convention:
|
||||
* - <group_prefix>-<media>.css
|
||||
*
|
||||
* This function aggregates all theme files in their own group, whatever is the
|
||||
* user configuration, to ensure no theme conflicts if the site as more than
|
||||
* one themes (most sites will have at least two, frontend and backend themes).
|
||||
*
|
||||
* Ideal case is, all files have already been built, therefore $status and
|
||||
* $groups array matches each other. In this particular case, no I/O at all is
|
||||
* being done and only the drupal_render() array is built pragmatically using
|
||||
* the $status array content.
|
||||
*/
|
||||
function core_library_element_style_pre_render($elements) {
|
||||
global $theme_key;
|
||||
|
||||
// Status array contains filename as keys, and numeric keys as values which
|
||||
// can be any of those:
|
||||
// - -1 : No files, no need to rebuild.
|
||||
// - TRUE : File already built.
|
||||
// - empty : Nothing done, or built failed, need rebuild.
|
||||
$status = variable_get('core_library_css_status');
|
||||
|
||||
// Group to filename mapping. Each group can have one or more medias, we will
|
||||
// distinguish this later in the algorithm.
|
||||
$mapping = array(
|
||||
CSS_SYSTEM => 'system',
|
||||
CSS_THEME => 'theme-' . str_replace('_', '-', $theme_key),
|
||||
CSS_DEFAULT => 'default',
|
||||
);
|
||||
|
||||
// What do we need to rebuild, this array will contain groups as keys, and
|
||||
// $css sub-array for each of those groups.
|
||||
$rebuild = array();
|
||||
|
||||
// Determine how to build groups. This depends on the user configuration.
|
||||
// User configuration has been taken into account by hook_library_css_alter()
|
||||
// earlier during the normal execution flow. We only have to regroup those
|
||||
// using the core grouping algorithm to ensure we have different groups for
|
||||
// browser specific files.
|
||||
foreach (drupal_group_css($elements['#items']) as $core_group) {
|
||||
$map = NULL;
|
||||
|
||||
// Scan for external file, we won't handle ourselves.
|
||||
if ($core_group['type'] == 'external') {
|
||||
// FIXME: Do something!
|
||||
}
|
||||
// Scan for browser specific rules. Remember that
|
||||
else if (!$core_group['browsers']['!IE']) {
|
||||
$map = $mapping[$core_group['group']] . '-' . str_replace(' ', '', $core_group['browsers']['IE']) . '-' . $core_group['media'];
|
||||
}
|
||||
// Normal processing (all browsers).
|
||||
else {
|
||||
$map = $mapping[$core_group['group']] . '-' . $core_group['media'];
|
||||
}
|
||||
|
||||
if ($map) {
|
||||
// Populate our group array, using core given groups. Those groups reflect
|
||||
// what we aggregated before at hook_library_css_alter() time, adding
|
||||
// browser information we omitted or let live with defaults.
|
||||
$groups[$map] = $core_group;
|
||||
|
||||
// Also mark the current for rebuild if needed.
|
||||
if (!isset($status[$map]) || !$status[$map]) {
|
||||
$rebuild[] = $map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild files we have to.
|
||||
if (!empty($rebuild)) {
|
||||
// Iterate over files, and rebuild the one we need.
|
||||
foreach ($rebuild as $map) {
|
||||
$status[$map] = _core_library_element_style_rebuild_group($map, $groups[$map]['items']);
|
||||
}
|
||||
|
||||
// Save our status (do not rebuild CSS later).
|
||||
variable_set('core_library_css_status', $status);
|
||||
}
|
||||
|
||||
// Build the final result for drupal_render().
|
||||
foreach ($groups as $map => $data) {
|
||||
// Do not include empty or non existing files. All files should be empty
|
||||
// or built when we get in this part of the algorithm.
|
||||
if ($status[$map] == TRUE) {
|
||||
// Compute the real filename using the same convention than the
|
||||
// _core_library_element_style_rebuild_group() function.
|
||||
$file = $map . '.css';
|
||||
|
||||
// Add the real element for drupal_render() function to include the tag.
|
||||
$elements[] = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'link',
|
||||
'#attributes' => array(
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet',
|
||||
'href' => file_create_url('public://static/' . $file),
|
||||
'media' => $data['media'],
|
||||
),
|
||||
'#browsers' => $data['browsers'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All ok.
|
||||
return $elements;
|
||||
}
|
15
sites/all/modules/core_library/core_library.info
Normal file
15
sites/all/modules/core_library/core_library.info
Normal file
@@ -0,0 +1,15 @@
|
||||
name = Core Library
|
||||
description = Extends Drupal core and allows you to override other modules' libraries definition and aggregation rules.
|
||||
package = Performance
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
configure = admin/config/development/library
|
||||
; Module files.
|
||||
files[] = core_library.module
|
||||
files[] = core_library.admin.inc
|
||||
; Information added by drupal.org packaging script on 2012-01-04
|
||||
version = "7.x-1.0-beta11"
|
||||
core = "7.x"
|
||||
project = "core_library"
|
||||
datestamp = "1325679340"
|
||||
|
100
sites/all/modules/core_library/core_library.install
Normal file
100
sites/all/modules/core_library/core_library.install
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library module schema.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function core_library_schema() {
|
||||
$schema = array();
|
||||
$schema['core_library_stat'] = array(
|
||||
'fields' => array(
|
||||
'lid' => array(
|
||||
'description' => 'Primary identifier',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'file' => array(
|
||||
'description' => 'File path',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'hits' => array(
|
||||
'description' => 'Hits count',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'hits_anonymous' => array(
|
||||
'description' => 'Anonymous hit',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'hits_admin' => array(
|
||||
'description' => 'Admin hit',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'module' => array(
|
||||
'description' => 'Module name, full path will be determined using its path',
|
||||
'type' => 'varchar',
|
||||
'length' => 64,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'type' => array(
|
||||
'description' => 'File type, can be CSS or JS, maybe something else',
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
),
|
||||
'primary key' => array('lid'),
|
||||
'unique keys' => array('file' => array('file')),
|
||||
);
|
||||
$schema['core_library_pages'] = array(
|
||||
'fields' => array(
|
||||
'lid' => array(
|
||||
'description' => 'Primary identifier',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'path' => array(
|
||||
'description' => 'Drupal path',
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
),
|
||||
'primary key' => array('lid', 'path'),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the 'exclude' flag into already learnt files to avoid PHP warnings.
|
||||
*/
|
||||
function core_library_update_7001() {
|
||||
variable_set('core_library_updated', TRUE);
|
||||
|
||||
$files = variable_get('library_aggregation_orphans', array());
|
||||
if (!empty($files)) {
|
||||
foreach ($files as $type => $array) {
|
||||
foreach ($array as $file => $options) {
|
||||
$files[$type][$file]['exclude'] = FALSE;
|
||||
}
|
||||
}
|
||||
variable_set('library_aggregation_orphans', $files);
|
||||
}
|
||||
}
|
90
sites/all/modules/core_library/core_library.manual.inc
Normal file
90
sites/all/modules/core_library/core_library.manual.inc
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library manual mode specifics functions.
|
||||
*
|
||||
* This code will be used only in manual inclusion mode. When the site admin
|
||||
* decides that his configuration is solid enough, he will set the bypass mode
|
||||
* and these functions won't ever be called anymore.
|
||||
*
|
||||
* This code tends to disappear in the future, but this is still solid code and
|
||||
* a nice PoC.
|
||||
*/
|
||||
|
||||
function core_library_manual_init() {
|
||||
// Include required file, we may be the first to get here.
|
||||
require_once DRUPAL_ROOT . '/includes/common.inc';
|
||||
|
||||
// Fetch site admin manually set settings.
|
||||
$settings = variable_get('library_aggregation_settings', array());
|
||||
|
||||
foreach ($settings as $name => $values) {
|
||||
if ($values['mode'] == LIBRARY_AGGREGATE_ALL) {
|
||||
core_library_manual_process($values['module'], $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process library depedencies.
|
||||
*
|
||||
* This function will be used only in manual mode. If you are in learning mode
|
||||
* all library files will be found and overriden at hook_library_TYPE_alter()
|
||||
* time.
|
||||
*
|
||||
* Gather all libraries override for all pages including. Pragmatically set
|
||||
* all of them using drupal_add_js(), but set the drupal_static() cache of
|
||||
* drupal_add_library() first to avoid any hook calls for theses.
|
||||
*/
|
||||
function core_library_manual_process($module, $name) {
|
||||
// Bypass drupal_static() anti-pattern. All javascript and CSS we are going
|
||||
// to add will exists for the full runtime.
|
||||
static $processed = array(), $added;
|
||||
|
||||
if (!isset($added)) {
|
||||
// We will populate the drupal_add_library() static cache ourself by adding
|
||||
// javascript and CSS files with our own algorithm.
|
||||
$added = &drupal_static('drupal_add_library', array());
|
||||
}
|
||||
|
||||
// Security check, sometimes, $module is an array (but why?).
|
||||
if (!is_string($module) || !is_string($name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This will trigger the drupal_alter() over libraries.
|
||||
$library = drupal_get_library($module, $name);
|
||||
|
||||
// Process dependencies recursively. Because dependencies will always remains
|
||||
// harcoded, the result will always be the same resulting in the same files
|
||||
// being aggregated at each page hit.
|
||||
if (isset($library['dependencies'])) {
|
||||
foreach ($library['dependencies'] as $dependency) {
|
||||
list($_module, $_name) = $dependency;
|
||||
if (!isset($processed[$_module . '-' . $_name])) {
|
||||
core_library_manual_process($_module, $_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force settings for JS and CSS files to ensure the aggregation.
|
||||
if (isset($library['js'])) {
|
||||
foreach ($library['js'] as $file => $data) {
|
||||
core_library_defaults_js($data);
|
||||
drupal_add_js($file, $data);
|
||||
}
|
||||
}
|
||||
if (isset($library['css'])) {
|
||||
foreach ($library['css'] as $file => $data) {
|
||||
$data['data'] = $file;
|
||||
core_library_defaults_css($data);
|
||||
drupal_add_css($file, $data);
|
||||
}
|
||||
}
|
||||
|
||||
// Register the library to the core.
|
||||
$added[$module][$name] = TRUE;
|
||||
// Circular dependency and infinite recursivity breaker.
|
||||
$processed[$module . '-' . $name] = TRUE;
|
||||
}
|
516
sites/all/modules/core_library/core_library.module
Normal file
516
sites/all/modules/core_library/core_library.module
Normal file
@@ -0,0 +1,516 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Aggregate file on all pages.
|
||||
*/
|
||||
define('LIBRARY_AGGREGATE_ALL', 1);
|
||||
|
||||
/**
|
||||
* Aggregate file on a per-path basis.
|
||||
*/
|
||||
define('LIBRARY_AGGREGATE_PATH', 2);
|
||||
|
||||
/**
|
||||
* Let core follow its own rules for this file aggregation.
|
||||
*/
|
||||
define('LIBRARY_AGGREGATE_DEFAULT', 3);
|
||||
|
||||
/**
|
||||
* Site admin defined aggregation rules.
|
||||
*/
|
||||
define('LIBRARY_MODE_MANUAL', 1);
|
||||
|
||||
/**
|
||||
* Learn and aggregate all files in anonymous browsing.
|
||||
*/
|
||||
define('LIBRARY_MODE_LEARNING_ANONYMOUS', 2);
|
||||
|
||||
/**
|
||||
* Learn and aggregate all files in browsing, admin pages excluded.
|
||||
*/
|
||||
define('LIBRARY_MODE_LEARNING_NO_ADMIN', 3);
|
||||
|
||||
/**
|
||||
* Learn and aggregate all files.
|
||||
*/
|
||||
define('LIBRARY_MODE_LEARNING_ALL', 4);
|
||||
|
||||
/**
|
||||
* Do not, ever, let the core aggregate files, and use the latest one whatever
|
||||
* happens. This means all new files will be ignored.
|
||||
*/
|
||||
define('LIBRARY_MODE_BYPASS', 5);
|
||||
|
||||
/**
|
||||
* Group CSS in 2 groups, all around in one side, theme specifics on the other.
|
||||
*/
|
||||
define('LIBRARY_CSS_GROUP_DUAL', 1);
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function core_library_menu() {
|
||||
$items = array();
|
||||
$items['admin/config/development/library'] = array(
|
||||
'title' => "Library",
|
||||
'description' => "Manage external JS and CSS libraries and allows to set different aggregation rules.",
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('core_library_admin_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_NORMAL_ITEM,
|
||||
'file' => 'core_library.admin.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSMin path.
|
||||
*
|
||||
* FIXME: Sorry, this is hardcoded right now, will change in the future.
|
||||
*
|
||||
* @return string
|
||||
* Found library, NULL if not found.
|
||||
*/
|
||||
function core_library_js_minify_library_path() {
|
||||
if ($path = variable_get('core_library_jsmin_path', NULL)) {
|
||||
return $path;
|
||||
}
|
||||
else {
|
||||
return 'sites/all/libraries/jsmin/jsmin.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify given JS if not already done.
|
||||
*/
|
||||
function core_library_js_minify(&$options) {
|
||||
static $minified, $included = FALSE;
|
||||
|
||||
if (!isset($minified)) {
|
||||
$minified = variable_get('core_library_js_minified', array());
|
||||
}
|
||||
|
||||
// Code readability, my friend.
|
||||
$source = $options['data'];
|
||||
|
||||
// We should allow exclusion here, user configured.
|
||||
if (!array_key_exists($source, $minified)) {
|
||||
|
||||
// Do not compress already compressed files.
|
||||
if (!strpos($source, '.min.js')) {
|
||||
|
||||
// Attempt to use jsmin library if present.
|
||||
if (!$included) {
|
||||
$libpath = core_library_js_minify_library_path();
|
||||
require_once DRUPAL_ROOT . '/' . $libpath;
|
||||
$included = TRUE;
|
||||
}
|
||||
|
||||
// Why does this functions is not wrapped in file.inc? It should, since we
|
||||
// are supposed to manipulate every file using stream wrappers.
|
||||
$buf = file_get_contents($source);
|
||||
$buf = JSMin::minify($buf);
|
||||
|
||||
// Compute new filename.
|
||||
$parts = explode('/', $source);
|
||||
$filename = end($parts);
|
||||
$file_uri = 'public://static/' . str_replace('.js', '.min.js', $filename);
|
||||
|
||||
// Save it.
|
||||
if ($file_uri = file_unmanaged_save_data($buf, $file_uri, FILE_EXISTS_REPLACE)) {
|
||||
if ($wrapper = file_stream_wrapper_get_instance_by_uri($file_uri)) {
|
||||
// Protected getTarget() function emulation.
|
||||
list($scheme, $target) = explode('://', $file_uri, 2);
|
||||
$target = trim($target, '\/');
|
||||
$file_uri = $wrapper->getDirectoryPath() . '/' . $target;
|
||||
}
|
||||
|
||||
watchdog('core_library', "File '@src' minified successfully to '@dest'.", array(
|
||||
'@src' => $source,
|
||||
'@dest' => $file_uri,
|
||||
));
|
||||
|
||||
// Save is ok, put the new filename in the $options array
|
||||
$options['data'] = $file_uri;
|
||||
|
||||
// Tell the system we minified a new file.
|
||||
$minified[$source] = $file_uri;
|
||||
variable_set('core_library_js_minified', $minified);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We have already minified this file in the past, let's reuse the cached
|
||||
// result path for alteration.
|
||||
$options['data'] = $minified[$source];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the given JavaScript description to enforce it to go into the main
|
||||
* libraries group, so the core will create only one big aggregated file.
|
||||
*/
|
||||
function core_library_defaults_js(&$data) {
|
||||
// Custom override.
|
||||
static $minify_enabled = NULL, $defaults = array(
|
||||
'every_page' => TRUE,
|
||||
'preprocess' => TRUE,
|
||||
'cache' => TRUE,
|
||||
'defer' => FALSE,
|
||||
'type' => 'file',
|
||||
'scope' => 'header',
|
||||
);
|
||||
|
||||
// Ignore 'inline' or 'external' content.
|
||||
// Not set type means we are getting this entry from our own cache.
|
||||
if (isset($data['type']) && 'file' !== $data['type']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare some variables at first hit.
|
||||
if (!isset($minify_enabled)) {
|
||||
$minify_enabled = variable_get('core_library_js_minify', FALSE);
|
||||
}
|
||||
|
||||
// Set default weight for every file. If we don't drupal_get_css() will
|
||||
// fail at uasort() time on custom added JS files.
|
||||
if (!array_key_exists('weight', $data)) {
|
||||
$data['weight'] = 0;
|
||||
}
|
||||
|
||||
// First thing to do is to force the current group to be libraries one.
|
||||
if (!array_key_exists('group', $data) || $data['group'] != JS_LIBRARY) {
|
||||
$data['group'] = JS_LIBRARY;
|
||||
|
||||
// Once we done that, we have to alter weight to ensure no outsider will
|
||||
// take precedence over real libraries.
|
||||
$data['weight'] += 1000;
|
||||
}
|
||||
|
||||
// Override data.
|
||||
foreach ($defaults as $name => $value) {
|
||||
$data[$name] = $value;
|
||||
}
|
||||
|
||||
// Minify, if enabled.
|
||||
if ($minify_enabled) {
|
||||
core_library_js_minify($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the given CSS description to enforce it to go into the main
|
||||
* system group, so the core will create only one big aggregated file.
|
||||
*/
|
||||
function core_library_defaults_css(&$data) {
|
||||
// Defaults won't handle the 'media' key. Modules and libraries do specify
|
||||
// their own value for this (we only set the 'all' value when no value is
|
||||
// given).
|
||||
static $please_group_css, $defaults = array(
|
||||
'type' => 'file',
|
||||
'every_page' => TRUE,
|
||||
'preprocess' => TRUE,
|
||||
);
|
||||
|
||||
// Ignore 'inline' or 'external' content.
|
||||
// Not set type means we are getting this entry from our own cache.
|
||||
if (isset($data['type']) && 'file' !== $data['type']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Statically initialize variables used for the rest of the algorithm.
|
||||
if (!isset($please_group_css)) {
|
||||
$css_grouping_mode = variable_get('core_library_css_group', FALSE);
|
||||
$please_group_css = (bool) $css_grouping_mode;
|
||||
}
|
||||
|
||||
// Assign a default group for files that does not have any. This can happen
|
||||
// within our own execution workflow because we override a lot of stuff from
|
||||
// core.
|
||||
if (!array_key_exists('group', $data)) {
|
||||
$data['group'] = CSS_DEFAULT;
|
||||
}
|
||||
|
||||
// In some case, browsers are not specified, which make the aggregation
|
||||
// algorithm to have a specific group for those. We don't want that, no
|
||||
// browser specified means all browsers, like any other files.
|
||||
// This is a weird behavior since it happens only for some core files
|
||||
// included with the drupal_add_css() $every_page parameter set to TRUE,
|
||||
// see system_init() implementation for example.
|
||||
if (empty($data['browsers'])) {
|
||||
$data['browsers'] = array(
|
||||
'IE' => TRUE,
|
||||
'!IE' => TRUE,
|
||||
);
|
||||
}
|
||||
// Same bug as above, for media.
|
||||
if (empty($data['media'])) {
|
||||
$data['media'] = 'all';
|
||||
}
|
||||
|
||||
// Set default weight for every file. If we don't drupal_get_css() will
|
||||
// fail at uasort() time on custom added JS files.
|
||||
if (!array_key_exists('weight', $data)) {
|
||||
$data['weight'] = 0;
|
||||
}
|
||||
|
||||
// First thing to do is to force the current group to be libraries one.
|
||||
if ($please_group_css) {
|
||||
if ($data['group'] != CSS_THEME) {
|
||||
$data['group'] = CSS_SYSTEM;
|
||||
|
||||
// Once we done that, we have to alter weight to ensure no outsider will
|
||||
// take precedence over real libraries.
|
||||
$data['weight'] += 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// Override data.
|
||||
foreach ($defaults as $name => $value) {
|
||||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function core_library_bypass_mode() {
|
||||
// Compat fix with overlay module.
|
||||
// FIXME: A better solution should be developped here.
|
||||
if (module_exists('overlay') && user_access('access-overlay')) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return variable_get('core_library_bypass', NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_element_info_alter().
|
||||
*
|
||||
* Depending on the actual aggregation mode we are, we will change the current
|
||||
* JS / CSS aggregation functions to ensure Drupal won't ever, ever attempt any
|
||||
* file_exists() or hash compute while we are sure our CSS and JS files are
|
||||
* stable and cover the full site.
|
||||
*/
|
||||
function core_library_element_info_alter(&$type) {
|
||||
// Change the 'style' element type rendering if needed.
|
||||
if (core_library_bypass_mode() == LIBRARY_MODE_BYPASS) {
|
||||
|
||||
// Force our file to be included if needed.
|
||||
require_once drupal_get_path('module', 'core_library') . '/core_library.bypass.inc';
|
||||
|
||||
$type['styles'] = array(
|
||||
'#items' => array(),
|
||||
'#pre_render' => array('core_library_element_style_pre_render'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_init().
|
||||
*/
|
||||
function core_library_init() {
|
||||
// We will aggregate libraries files only if are not in learning mode.
|
||||
// Learning mode will find them as classic files, without having to known
|
||||
// if they are part of a library or not.
|
||||
if (core_library_bypass_mode() == LIBRARY_MODE_MANUAL) {
|
||||
require_once drupal_get_path('module', 'core_library') . '/core_library.manual.inc';
|
||||
core_library_manual_init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single point where all exclusion rules happen.
|
||||
*
|
||||
* @param string $type
|
||||
* Library type (e.g. 'js' or 'css').
|
||||
* @param string $file
|
||||
* Original library key in libraries array.
|
||||
* @param array $options
|
||||
* Original library options.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if should not preprocess this file ourselves.
|
||||
*/
|
||||
function _core_library_must_exclude($type, $file, $options) {
|
||||
static $respectful_preprocess;
|
||||
|
||||
if (!isset($js_respect_preprocess)) {
|
||||
$respectful_preprocess = variable_get('core_library_respectful_preprocess', TRUE);
|
||||
}
|
||||
|
||||
if ($respectful_preprocess && isset($options['preprocess']) && !$options['preprocess']) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given array using the given type.
|
||||
*
|
||||
* This is the magic function where all learning happens.
|
||||
*/
|
||||
function _core_library_merge(&$array, $type) {
|
||||
global $theme_key;
|
||||
|
||||
// Following static variables are pure optimization.
|
||||
static $do_learn, $theme_path, $theme_len;
|
||||
|
||||
// Set the current learning mode.
|
||||
if (!isset($do_learn)) {
|
||||
switch (core_library_bypass_mode()) {
|
||||
|
||||
case LIBRARY_MODE_LEARNING_ANONYMOUS:
|
||||
$do_learn = user_is_anonymous();
|
||||
break;
|
||||
|
||||
case LIBRARY_MODE_LEARNING_NO_ADMIN:
|
||||
$do_learn = !drupal_match_path($_GET['q'], "admin\nadmin/*");
|
||||
break;
|
||||
|
||||
case LIBRARY_MODE_LEARNING_ALL:
|
||||
$do_learn = TRUE;
|
||||
break;
|
||||
|
||||
case LIBRARY_MODE_BYPASS:
|
||||
// In bypass mode, we still need libraries to be ordered in order for
|
||||
// our custom style pre_render function to build correct CSS files.
|
||||
$do_learn = FALSE;
|
||||
break;;
|
||||
|
||||
default:
|
||||
// We should never get here, but always keep a fallback.
|
||||
$exclude_path = "*";
|
||||
break;
|
||||
}
|
||||
|
||||
// For later use.
|
||||
$theme_path = drupal_get_path('theme', $theme_key);
|
||||
$theme_len = strlen($theme_path);
|
||||
}
|
||||
|
||||
$orphans = variable_get('library_aggregation_orphans', array());
|
||||
|
||||
// Callback for defaults.
|
||||
$function = 'core_library_defaults_' . $type;
|
||||
|
||||
// Failsafe.
|
||||
if (!is_callable($function)) {
|
||||
return;
|
||||
}
|
||||
if (!isset($orphans[$type])) {
|
||||
// array_diff_key() will require an array here when in learning mode, else
|
||||
// the function will fail returning an empty result.
|
||||
$orphans[$type] = array();
|
||||
}
|
||||
|
||||
// In case we are in learning mode, add the new file to manual override
|
||||
// and save result. The few first hit will cause some SQL overhead and
|
||||
// some cache rebuild while users browse, but when all files will be
|
||||
// found, this won't happen anymore.
|
||||
// Once the maximum files are being found, you should then switch to manual
|
||||
// mode, it will use the exact same variables we are being populating here
|
||||
// and will won't attempt to do the array difference here.
|
||||
// We do it before the real file alteration to ensure that newly found files
|
||||
// will also be aggregated the first hit.
|
||||
if ($do_learn) {
|
||||
$unknown = array_diff_key($array, $orphans[$type]);
|
||||
|
||||
// Avoid to store JS settings array, this could cause some unwanted JS
|
||||
// settings to displayed on wrong pages.
|
||||
unset($unknown['settings']);
|
||||
|
||||
// Proceed only if array is not empty.
|
||||
if (!empty($unknown)) {
|
||||
foreach ($unknown as $file => $options) {
|
||||
$data = array(
|
||||
'mode' => LIBRARY_AGGREGATE_ALL,
|
||||
'group' => $options['group'],
|
||||
'exclude' => _core_library_must_exclude($type, $file, $options),
|
||||
);
|
||||
// No exclusion mode forces us to keep the full original options
|
||||
// instead of allowing us to override it. This also means that the
|
||||
// file will still always be included whatever happens.
|
||||
if ($data['exclude']) {
|
||||
$data += $options;
|
||||
}
|
||||
$orphans[$type][$file] = $data;
|
||||
}
|
||||
|
||||
// Save new orphans.
|
||||
variable_set('library_aggregation_orphans', $orphans);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge array with our own values.
|
||||
foreach ($orphans[$type] as $file => $options) {
|
||||
|
||||
// Whatever happens, do not include files from another theme. We must
|
||||
// proceed this check as a side effect of learning all files, including
|
||||
// those from other themes.
|
||||
if ('css' === $type && isset($options['group']) && CSS_THEME === $options['group'] && strncmp($theme_path, $file, $theme_len)) {
|
||||
unset($array[$file]);
|
||||
}
|
||||
|
||||
else {
|
||||
// If entry does not already exists, add it in order for the core to use
|
||||
// it for full aggregation.
|
||||
if (!isset($array[$file])) {
|
||||
$array[$file] = $options + array('data' => $file);
|
||||
}
|
||||
|
||||
if (!$options['exclude']) {
|
||||
// Merge already loaded with our own options to force aggregation.
|
||||
$function($array[$file]);
|
||||
|
||||
// File destination may have changed.
|
||||
if ($array[$file]['data'] != $file) {
|
||||
$array[$array[$file]['data']] = $array[$file];
|
||||
unset($array[$file]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_js_alter().
|
||||
*/
|
||||
function core_library_js_alter(&$javascript) {
|
||||
// We are going to use this hook instead of the hook_init() implementation
|
||||
// in order to force some settings, because other drupal_add_js() calls will
|
||||
// override our changes.
|
||||
if (core_library_bypass_mode()) {
|
||||
_core_library_merge($javascript, 'js');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_css_alter().
|
||||
*/
|
||||
function core_library_css_alter(&$css) {
|
||||
// We are going to use this hook instead of the hook_init() implementation
|
||||
// in order to force some settings, because other drupal_add_js() calls will
|
||||
// override our changes.
|
||||
if (core_library_bypass_mode()) {
|
||||
_core_library_merge($css, 'css');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function core_library_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/config/development/library':
|
||||
$messages[] = t("This page allows you to configure advanced CSS and JS files aggregation rules. The Core Library module assumes that aggregating all CSS and JS files together on every page -even if they are not being always used- will provide better CSS and JS files cache hit over a long term usage and save more bandwith than core conditional aggregation algorithm.");
|
||||
$messages[] = t("Notice that when changing the aggregation mode you need to refresh your aggressive cache page, if enabled. The module won't do it by itself and will let you do so to ensure it won't happen during frequentation pikes.");
|
||||
if (variable_get('core_library_updated', FALSE)) {
|
||||
drupal_set_message(t("The Core Library module has been updated and requires you to reset the learnt files so far. If you don't experience PHP notices, CSS malfunctions, or JS errors you can safely ignore this message."), 'error');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library advanced UI module administration pages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get known orphans by removing the known library files.
|
||||
*/
|
||||
function _core_library_get_known_orphans() {
|
||||
// Gather all already known files for orphans exclusion.
|
||||
$known = array();
|
||||
$hook = 'library';
|
||||
foreach (module_implements('library') as $module) {
|
||||
foreach (module_invoke($module, $hook) as $name => $library) {
|
||||
if (isset($library['js'])) {
|
||||
foreach ($library['js'] as $file => $data) {
|
||||
$file = drupal_get_path('module', $module) . '/' . $file;
|
||||
$known[$file] = TRUE;
|
||||
}
|
||||
}
|
||||
if (isset($library['css'])) {
|
||||
foreach ($library['css'] as $file => $data) {
|
||||
$file = drupal_get_path('module', $module) . '/' . $file;
|
||||
$known[$file] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$select = db_select('core_library_stat', 'l');
|
||||
$select->join('system', 's', 's.name = l.module');
|
||||
$select->addField('s', 'type', 'owner_type');
|
||||
return $select
|
||||
->fields('l')
|
||||
->condition('file', array_keys($known), 'NOT IN')
|
||||
->orderBy('l.module', 'ASC')
|
||||
->orderBy('l.type', 'ASC')
|
||||
->orderBy('l.file', 'ASC')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Orphans administration screen.
|
||||
*/
|
||||
function core_library_admin_orphans($form, &$form_state) {
|
||||
$form['#tree'] = TRUE;
|
||||
|
||||
// Abitrary do some packages. We will populate it then build a full array
|
||||
// for HTML build.
|
||||
$form['orphans'] = array();
|
||||
|
||||
// Known orphans.
|
||||
$orphans = variable_get('library_aggregation_orphans', array());
|
||||
|
||||
// Core fixed files that will exists on all pages whatever happens.
|
||||
$fixed = array('misc/drupal.js');
|
||||
|
||||
foreach (_core_library_get_known_orphans() as $row) {
|
||||
$subform = array();
|
||||
|
||||
// Rows that come back from database are module path agnostic, we need to
|
||||
// convert them to fit our real site here.
|
||||
if ($row->module != 'core') {
|
||||
$row->file = drupal_get_path($row->owner_type, $row->module) . '/' . $row->file;
|
||||
}
|
||||
|
||||
// Some metadata for display purpose only.
|
||||
$subform['row'] = array('#type' => 'value', '#value' => $row);
|
||||
|
||||
// Other needed stuff.
|
||||
$subform['type'] = array('#type' => 'value', '#value' => $row->type);
|
||||
$subform['module'] = array('#type' => 'value', '#value' => $row->module);
|
||||
// The most important stuff: the mode selection.
|
||||
$subform['mode'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t("Aggregation mode"),
|
||||
'#options' => array(
|
||||
LIBRARY_AGGREGATE_ALL => t("All"),
|
||||
// FIXME: Disabled, this was a (not so) good idea.
|
||||
// LIBRARY_AGGREGATE_PATH => t("Page"),
|
||||
LIBRARY_AGGREGATE_DEFAULT => t("Default"),
|
||||
),
|
||||
'#default_value' => isset($orphans[$row->type][$row->file]['mode']) ? $orphans[$row->type][$row->file]['mode'] : LIBRARY_AGGREGATE_DEFAULT,
|
||||
);
|
||||
|
||||
// Disable some fixed libraries (that core will append to aggregation
|
||||
// whatever happens).
|
||||
if (in_array($row->file, $fixed)) {
|
||||
$subform['mode']['#default_value'] = LIBRARY_AGGREGATE_ALL;
|
||||
$subform['mode']['#disabled'] = TRUE;
|
||||
}
|
||||
|
||||
// All done.
|
||||
$form['orphans'][$row->module][$row->file] = $subform;
|
||||
}
|
||||
|
||||
$form['buttons'] = array();
|
||||
$form['buttons']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t("Save"),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
function core_library_admin_orphans_submit($form, &$form_state) {
|
||||
$orphans = array();
|
||||
foreach ($form_state['values']['orphans'] as $module => $files) {
|
||||
foreach ($files as $file => $values) {
|
||||
// Add to our variable only if we need to.
|
||||
if ($values['mode'] != LIBRARY_AGGREGATE_DEFAULT) {
|
||||
$orphans[$values['type']][$file] = array(
|
||||
'mode' => $values['mode'],
|
||||
'module' => $module,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
variable_set('library_aggregation_orphans', $orphans);
|
||||
drupal_set_message(t("Libraries aggregation settings have been saved"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Libraries administration screen.
|
||||
*/
|
||||
function core_library_admin_libraries($form, &$form_state) {
|
||||
$form['#tree'] = TRUE;
|
||||
|
||||
// Abitrary do some packages. We will populate it then build a full array
|
||||
// for HTML build.
|
||||
$form['libraries'] = array(
|
||||
'core' => array(),
|
||||
'effect' => array(),
|
||||
'ui' => array(),
|
||||
'contrib' => array(),
|
||||
);
|
||||
|
||||
// Get current existing settings.
|
||||
$settings = variable_get('library_aggregation_settings', array());
|
||||
|
||||
$hook = 'library';
|
||||
foreach (module_implements('library') as $module) {
|
||||
foreach (module_invoke($module, $hook) as $name => $library) {
|
||||
$subform = array();
|
||||
$subform['mode'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t("Aggregation mode"),
|
||||
'#options' => array(
|
||||
LIBRARY_AGGREGATE_ALL => t("All"),
|
||||
LIBRARY_AGGREGATE_PATH => t("Page"),
|
||||
LIBRARY_AGGREGATE_DEFAULT => t("Default"),
|
||||
),
|
||||
'#default_value' => isset($settings[$name]['mode']) ? $settings[$name]['mode'] : LIBRARY_AGGREGATE_DEFAULT,
|
||||
);
|
||||
$subform['description'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $library,
|
||||
);
|
||||
$subform['module'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $module,
|
||||
);
|
||||
// Find out type.
|
||||
if ($name == 'ui' || substr($name, 0, 3) == 'ui.') {
|
||||
$type = 'ui';
|
||||
}
|
||||
else if ($name == 'effects' || substr($name, 0, 8) == 'effects.') {
|
||||
$type = 'effect';
|
||||
}
|
||||
else if ($module == 'system') {
|
||||
$type = 'core';
|
||||
}
|
||||
else {
|
||||
$type = 'contrib';
|
||||
}
|
||||
// All done.
|
||||
$form['libraries'][$type][$name] = $subform;
|
||||
}
|
||||
}
|
||||
|
||||
$form['buttons'] = array();
|
||||
$form['buttons']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t("Save"),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
function core_library_admin_libraries_submit($form, &$form_state) {
|
||||
$settings = array();
|
||||
foreach ($form_state['values']['libraries'] as $type => $libraries) {
|
||||
foreach ($libraries as $name => $values) {
|
||||
$settings[$name] = array(
|
||||
'mode' => $values['mode'],
|
||||
'skip_alter' => isset($values['skip_alter']) ? $values['skip_alter'] : FALSE,
|
||||
'module' => $values['module'],
|
||||
);
|
||||
}
|
||||
}
|
||||
variable_set('library_aggregation_settings', $settings);
|
||||
drupal_set_message(t("Libraries aggregation settings have been saved"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Export profile page.
|
||||
*/
|
||||
function core_library_admin_export_form($form, &$form_state, $callback) {
|
||||
$form['export'] = array(
|
||||
'#type' => 'markup',
|
||||
'#prefix' => '<pre id="export-text" style="background: white; color: black; padding: 0.5em 1em; border: 1px solid black;">',
|
||||
'#suffix' => '</pre>',
|
||||
'#markup' => is_callable($callback) ? $callback() : '',
|
||||
);
|
||||
if (module_exists('oox')) {
|
||||
$form['#attached'] = array('js' => array(
|
||||
drupal_get_path('module', 'oox') . '/js/jquery.copy.min.js',
|
||||
drupal_get_path('module', 'oox') . '/js/copy.js',
|
||||
));
|
||||
$form['clip'] = array(
|
||||
'#type' => 'button',
|
||||
'#value' => t("Copy to clipboard"),
|
||||
'#attributes' => array('class' => array('copy-to-clipboard'), 'rel' => 'export-text'),
|
||||
);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all known files.
|
||||
*/
|
||||
function core_library_known_file_export() {
|
||||
$code = array();
|
||||
|
||||
$code[] = "; This file was generated using the Core Library module.";
|
||||
$code[] = "; Built list of already known files for later use.";
|
||||
|
||||
foreach (_core_library_get_known_orphans() as $row) {
|
||||
$code[] = $row->module . "[" . $row->type . "][] = " . $row->file;
|
||||
}
|
||||
|
||||
return implode("\n", $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export current profile.
|
||||
*/
|
||||
function core_library_profile_export_current() {
|
||||
$code = array();
|
||||
|
||||
$settings = variable_get('library_aggregation_settings', array());
|
||||
$orphans = variable_get('library_aggregation_orphans', array());
|
||||
|
||||
$code[] = "; This file was generated using the Core Library module.";
|
||||
$code[] = "; Libraries.";
|
||||
uasort($settings, '_core_library_sort_by_module');
|
||||
|
||||
foreach ($settings as $name => $options) {
|
||||
if ($options['mode'] == LIBRARY_AGGREGATE_ALL) {
|
||||
$code[] = "libraries[" . $options['module'] . "][] = " . $name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($orphans as $type => $items) {
|
||||
$code[] = "; " . strtoupper($type) . " files.";
|
||||
foreach ($items as $file => $options) {
|
||||
if ($options['mode'] == LIBRARY_AGGREGATE_ALL) {
|
||||
$code[] = $type . "[] = " . $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort options array by module.
|
||||
*/
|
||||
function _core_library_sort_by_module($a, $b) {
|
||||
$a_module = is_array($a) && isset($a['module']) ? $a['module'] : '';
|
||||
$b_module = is_array($b) && isset($b['module']) ? $b['module'] : '';
|
||||
return strcasecmp($a_module, $b_module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a percent value for some tables.
|
||||
*/
|
||||
function _core_library_format_percent($max, $value) {
|
||||
return $value . " (" . round($value / $max * 100) . "%)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Orphan settings form theming function.
|
||||
*/
|
||||
function theme_core_library_admin_orphans(&$variables) {
|
||||
$elements = array('#type' => 'vertical_tabs');
|
||||
$form = &$variables['form'];
|
||||
|
||||
// Headers will be the same for each table.
|
||||
$headers = array(t("File"), t("Module"), t("Type"), t("Mode"), t("Hits"), t("Admin hits"), t("Anonymous hits"));
|
||||
|
||||
$tables = array();
|
||||
foreach (element_children($form['orphans']) as $module) {
|
||||
foreach (element_children($form['orphans'][$module]) as $file) {
|
||||
$frow = $form['orphans'][$module][$file]['row']['#value'];
|
||||
$row = array('data' => array());
|
||||
$row['data'][] = $file;
|
||||
$row['data'][] = $module;
|
||||
unset($form['orphans'][$module][$file]['mode']['#title']);
|
||||
$row['data'][] = $frow->type;
|
||||
$row['data'][] = drupal_render($form['orphans'][$module][$file]['mode']);
|
||||
$row['data'][] = $frow->hits;
|
||||
$row['data'][] = _core_library_format_percent($frow->hits, $frow->hits_admin);
|
||||
$row['data'][] = _core_library_format_percent($frow->hits, $frow->hits_anonymous);
|
||||
$tables[$frow->owner_type][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
// New elements.
|
||||
foreach ($tables as $owner_type => $rows) {
|
||||
$elements[$owner_type] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t(ucfirst($owner_type)),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#group' => 'owner_type',
|
||||
'content' => array(
|
||||
// F-ing theme system. Why wouldn't it use my theme function here? Because
|
||||
// it changes the variable to something other than the theme function will
|
||||
// understand, with absolutely no reasons!
|
||||
//'#type' => 'markup',
|
||||
// '#theme' => 'table',
|
||||
'#markup' => theme('table', array('header' => $headers, 'rows' => $rows))
|
||||
// 'header' => $headers,
|
||||
// 'rows' => $rows,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return drupal_render($elements) . drupal_render_children($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Libraries settings form theming function.
|
||||
*/
|
||||
function theme_core_library_admin_libraries(&$variables) {
|
||||
$elements = array('#type' => 'vertical_tabs');
|
||||
$form = &$variables['form'];
|
||||
|
||||
// Headers will be the same for each table.
|
||||
$headers = array(t("Library"), t("Identifier"), t("Module"), t("Mode"));
|
||||
|
||||
// Put different packages in different tables.
|
||||
foreach (element_children($form['libraries']) as $type) {
|
||||
$rows = array();
|
||||
foreach (element_children($form['libraries'][$type]) as $name) {
|
||||
$row = array('data' => array());
|
||||
$row['data'][] = $form['libraries'][$type][$name]['description']['#value']['title'];
|
||||
$row['data'][] = $name;
|
||||
$row['data'][] = $form['libraries'][$type][$name]['module']['#value'];
|
||||
unset($form['libraries'][$type][$name]['mode']['#title']);
|
||||
unset($form['libraries'][$type][$name]['skip_alter']['#title']);
|
||||
$row['data'][] = drupal_render($form['libraries'][$type][$name]['mode']);
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// New element.
|
||||
$elements[$type] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $type,
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#group' => 'library_types',
|
||||
'content' => array('#markup' => theme('table', array('header' => $headers, 'rows' => $rows))),
|
||||
);
|
||||
}
|
||||
|
||||
return drupal_render($elements) . drupal_render_children($form);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
name = Core Library Advanced UI
|
||||
description = Provide configuration screens for the Core Library module. You should enable this module the first time you use it, then disable it once in production.
|
||||
package = Performance
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
configure = admin/config/development/library
|
||||
; Module files.
|
||||
files[] = core_library_ui.module
|
||||
files[] = core_library_ui.admin.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-01-04
|
||||
version = "7.x-1.0-beta11"
|
||||
core = "7.x"
|
||||
project = "core_library"
|
||||
datestamp = "1325679340"
|
||||
|
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core Library advanced UI module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function core_library_ui_menu() {
|
||||
$items = array();
|
||||
$items['admin/config/development/library/options'] = array(
|
||||
'title' => "Settings",
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
);
|
||||
$items['admin/config/development/library/libraries'] = array(
|
||||
'title' => "Libraries",
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('core_library_admin_libraries'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'core_library_ui.admin.inc',
|
||||
);
|
||||
$items['admin/config/development/library/orphans'] = array(
|
||||
'title' => "Orphans",
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('core_library_admin_orphans'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'core_library_ui.admin.inc',
|
||||
);
|
||||
// Profile handling.
|
||||
$items['admin/config/development/library/profile/export'] = array(
|
||||
'title' => "Current profile export",
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('core_library_admin_export_form', 'core_library_profile_export_current'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'core_library_ui.admin.inc',
|
||||
);
|
||||
$items['admin/config/development/library/files/export'] = array(
|
||||
'title' => "Known files export",
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('core_library_admin_export_form', 'core_library_known_file_export'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'core_library_ui.admin.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme().
|
||||
*/
|
||||
function core_library_ui_theme() {
|
||||
$items = array();
|
||||
$items['core_library_admin_libraries'] = array(
|
||||
'file' => 'core_library_ui.admin.inc',
|
||||
'render element' => 'form',
|
||||
);
|
||||
$items['core_library_admin_orphans'] = array(
|
||||
'file' => 'core_library_ui.admin.inc',
|
||||
'render element' => 'form',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function core_library_ui_form_core_library_admin_settings_alter(&$form, &$form_state) {
|
||||
$form['advanced']['core_library_collect'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t("Collect statistics"),
|
||||
'#default_value' => variable_get('core_library_collect', FALSE),
|
||||
'#description' => t("This will collect statistics on JS and CSS files usage. Please do not enable this in production, it could have a major performance impact."),
|
||||
);
|
||||
if (module_exists('core_library_ui')) {
|
||||
$form['actions']['export_profile'] = array(
|
||||
'#type' => 'markup',
|
||||
'#markup' => l(t("Export profile"), 'admin/config/development/library/profile/export'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find file owner.
|
||||
*
|
||||
* This function may be unperformant, because we use the module list and theme
|
||||
* list from Drupal core, and store them statically, which means we are using
|
||||
* huge arrays duplicated in memory.
|
||||
*
|
||||
* I'm not quite happy with this function, but at least it works.
|
||||
*
|
||||
* @param string &$filename
|
||||
* Complete filename relative to Drupal root.
|
||||
*
|
||||
* @return array
|
||||
* Indexed values array:
|
||||
* - First value is the owner name, from system table
|
||||
* - Second value is the munge filename, relative to owner path, third value
|
||||
* - Third value is the owner type ('theme', 'module', or 'core')
|
||||
*/
|
||||
function _core_library_find_owner($filepath) {
|
||||
static $modules, $themes;
|
||||
|
||||
// Build static cache.
|
||||
if (!isset($modules)) {
|
||||
// First, load module list.
|
||||
$modules = array();
|
||||
foreach(module_list() as $module) {
|
||||
$modules[$module] = drupal_get_path('module', $module);
|
||||
}
|
||||
// Also check for themes.
|
||||
$themes = array();
|
||||
foreach (list_themes() as $theme => $info) {
|
||||
$themes[$theme] = drupal_get_path('theme', $theme);
|
||||
}
|
||||
}
|
||||
|
||||
// Prep. variables.
|
||||
$owner = $owner_type = NULL;
|
||||
|
||||
// Ok, so we are treating an existing file, let's find out what is its real
|
||||
// module behind, it may be either a module, a theme, or the core itself.
|
||||
foreach ($modules as $module => $path) {
|
||||
if (substr($filepath, 0, strlen($path)) == $path) {
|
||||
$owner = $module;
|
||||
$owner_type = 'module';
|
||||
$filepath = substr($filepath, strlen($path) + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Second, test with theme.
|
||||
if (!$owner) {
|
||||
foreach ($themes as $theme => $path) {
|
||||
if (substr($filepath, 0, strlen($path)) == $path) {
|
||||
$owner = $theme;
|
||||
$owner_type = 'theme';
|
||||
$filepath = substr($filepath, strlen($path) + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we have no module, consider this file belongs to core itself as
|
||||
// a misc. file.
|
||||
if (!$owner) {
|
||||
$owner = $owner_type = 'core';
|
||||
}
|
||||
|
||||
// Build return array.
|
||||
return array($owner, $filepath, $owner_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update statistics for given library file.
|
||||
*
|
||||
* This function seems to be really slow to run, this is probably due to the
|
||||
* number of update or insert requests being done. We are working on a varchar
|
||||
* indexed table which makes those requests even slower when using MySQL with
|
||||
* the InnoDB engine.
|
||||
*
|
||||
* In order to provide a powerful and usable statistic counter, we need to
|
||||
* rework this part.
|
||||
*
|
||||
* Maybe doing bulk insert at hook_exit() time would be better.
|
||||
*/
|
||||
function core_library_update_stats($file, $data, $type = 'unknown') {
|
||||
static $done = array(), $known;
|
||||
|
||||
// Skip settings.
|
||||
if ($file == 'settings') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not proceed the same file twice.
|
||||
if (isset($done[$file])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare some stuff.
|
||||
if (!isset($known)) {
|
||||
// This is a pure performance patch, load full list in order to avoid one
|
||||
// SQL query per file. The choice of consumming more memory in order to keep
|
||||
// performances have been made here.
|
||||
$known = array();
|
||||
$results = db_select('core_library_stat', 'cls')
|
||||
->fields('cls', array('file', 'module', 'lid'))
|
||||
->execute()
|
||||
->fetchAll();
|
||||
foreach ($results as $data) {
|
||||
$known[$data->module][$data->file] = $data->lid;
|
||||
}
|
||||
}
|
||||
|
||||
// Find owner.
|
||||
list($owner, $file) = _core_library_find_owner($file);
|
||||
|
||||
$is_admin = drupal_match_path($_GET['q'], "admin\nadmin/*");
|
||||
$is_anonymous = user_is_anonymous();
|
||||
|
||||
// Insert new file record, if not already set.
|
||||
if (!isset($known[$owner][$file])) {
|
||||
$record = array(
|
||||
'file' => $file,
|
||||
'hits' => 1,
|
||||
'hits_anonymous' => $is_anonymous ? 1 : 0,
|
||||
'hits_admin' => $is_admin ? 1 : 0,
|
||||
'module' => $owner,
|
||||
'type' => $type,
|
||||
);
|
||||
drupal_write_record('core_library_stat', $record);
|
||||
$lid = $record['lid'];
|
||||
$known[$owner][$file] = $lid;
|
||||
}
|
||||
else {
|
||||
$lid = $known[$owner][$file];
|
||||
// Increment the counter if the file already exists.
|
||||
$sets = array();
|
||||
$sets[] = "hits = hits + 1";
|
||||
if ($is_anonymous) {
|
||||
$sets[] = "hits_anonymous = hits_anonymous + 1";
|
||||
}
|
||||
if ($is_admin) {
|
||||
$sets[] = "hits_admin = hits_admin + 1";
|
||||
}
|
||||
db_query("UPDATE {core_library_stat} SET " . implode(", ", $sets) . " WHERE lid = :lid", array(':lid' => $lid));
|
||||
}
|
||||
|
||||
$done[$file] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_js_alter().
|
||||
*/
|
||||
function core_library_ui_js_alter(&$javascript) {
|
||||
// Aggregate statistics if configured for.
|
||||
if (variable_get('core_library_collect', FALSE)) {
|
||||
foreach ($javascript as $file => $data) {
|
||||
if ('file' === $data['type']) {
|
||||
core_library_update_stats($file, $data, 'js');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_css_alter().
|
||||
*/
|
||||
function core_library_ui_css_alter(&$css) {
|
||||
// Aggregate statistics if configured for.
|
||||
if (variable_get('core_library_collect', FALSE)) {
|
||||
foreach ($css as $file => $data) {
|
||||
if ('file' === $data['type']) {
|
||||
core_library_update_stats($file, $data, 'css');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user