From 0811f2c02b207cedf1e8c5849117985b5eeba4be Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 26 Jul 2017 13:29:42 +0200 Subject: [PATCH] New module 'ImageCache' --- sites/all/modules/imagecache/INSTALL.txt | 25 + sites/all/modules/imagecache/LICENSE.txt | 339 ++++ sites/all/modules/imagecache/README.txt | 69 + sites/all/modules/imagecache/UPGRADE.txt | 7 + .../all/modules/imagecache/imagecache.api.php | 102 ++ .../modules/imagecache/imagecache.drush.inc | 169 ++ sites/all/modules/imagecache/imagecache.info | 12 + .../all/modules/imagecache/imagecache.install | 322 ++++ sites/all/modules/imagecache/imagecache.js | 50 + .../all/modules/imagecache/imagecache.module | 1367 +++++++++++++++++ .../modules/imagecache/imagecache_actions.inc | 340 ++++ .../all/modules/imagecache/imagecache_ui.info | 13 + .../modules/imagecache/imagecache_ui.install | 4 + .../modules/imagecache/imagecache_ui.module | 160 ++ .../imagecache/imagecache_ui.pages.inc | 454 ++++++ sites/all/modules/imagecache/sample.png | Bin 0 -> 178285 bytes .../tests/imagecache_create_url.test | 169 ++ 17 files changed, 3602 insertions(+) create mode 100644 sites/all/modules/imagecache/INSTALL.txt create mode 100644 sites/all/modules/imagecache/LICENSE.txt create mode 100644 sites/all/modules/imagecache/README.txt create mode 100644 sites/all/modules/imagecache/UPGRADE.txt create mode 100644 sites/all/modules/imagecache/imagecache.api.php create mode 100644 sites/all/modules/imagecache/imagecache.drush.inc create mode 100644 sites/all/modules/imagecache/imagecache.info create mode 100644 sites/all/modules/imagecache/imagecache.install create mode 100644 sites/all/modules/imagecache/imagecache.js create mode 100644 sites/all/modules/imagecache/imagecache.module create mode 100644 sites/all/modules/imagecache/imagecache_actions.inc create mode 100644 sites/all/modules/imagecache/imagecache_ui.info create mode 100644 sites/all/modules/imagecache/imagecache_ui.install create mode 100644 sites/all/modules/imagecache/imagecache_ui.module create mode 100644 sites/all/modules/imagecache/imagecache_ui.pages.inc create mode 100644 sites/all/modules/imagecache/sample.png create mode 100644 sites/all/modules/imagecache/tests/imagecache_create_url.test diff --git a/sites/all/modules/imagecache/INSTALL.txt b/sites/all/modules/imagecache/INSTALL.txt new file mode 100644 index 0000000..2c0bc32 --- /dev/null +++ b/sites/all/modules/imagecache/INSTALL.txt @@ -0,0 +1,25 @@ +For ImageCache to work properly it needs to be able to make use fou clean URLs. +This requires some webserver specific setup. + +Apache2 + mod_rewrite + Works out of the box. + +LightHTTPD pre-1.4.24: + http://nordisch.org/2007/2/6/drupal-on-lighttpd-with-clean-urls + +LightHTTPD 1.4.24 and later: + https://veracium.com/content/imagecache-lighttpd-witho + +IIS 6: + add an error404.asp that passed everything to drupal. + + <% + Response.Expires=0 + strQString=Request.ServerVariables("QUERY_STRING") + If (InStr(strQString,"/d5/")) Then + pos=Instr(strQString,"/d5/")+3 + Id=Right(strQString,Len(strQString)-pos) + Response.Redirect("/d5/index.php?q=" & Id) + End If + Response.Redirect("index.htm") + %> diff --git a/sites/all/modules/imagecache/LICENSE.txt b/sites/all/modules/imagecache/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/sites/all/modules/imagecache/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/imagecache/README.txt b/sites/all/modules/imagecache/README.txt new file mode 100644 index 0000000..fe044a4 --- /dev/null +++ b/sites/all/modules/imagecache/README.txt @@ -0,0 +1,69 @@ +ImageCache is a dynamic image manipulation and cache tool. It allows you to +create a namespace that corresponds to a set of image manipulation actions. It +generates a derivative image the first time it is requested from a namespace +until the namespace or the entire imagecache is flushed. + +Getting Started: + +1. Upload and enable both the ImageCache and ImageCache UI modules. + +2. Go to Administer -> Site Building -> ImageCache. Click on the local task tab +labeled "Add New Preset" to build a new set of image manipulation actions. + +3. Enter a descriptive name of your choice (e.g. 'product_thumbnail') into the +"Preset Namespace" box and click "Create New Preset". + +4. Add actions to your preset that tell ImageCache how to manipulate the +original image when it is rendered for display. Available actions include +crop, scale, desaturate (grey scale), resize, and rotate. Multiple actions +may be added to a preset. + +5. Each action is configured in its own form, and the actions may be reordered +from the preset's configuration form. If you need to make any changes to the +order of actions in a preset, remember to click "Update Preset" when you're +finished. + +Viewing Manipulated Images: + +Your modified image can be viewed by visiting a URL in this format: + +http://example.com/files/imagecache/preset-name/files/image-name.jpg + +For example, if your preset is named 'product_thumbnail' and your image is +named 'green-widget.jpg', you could view your modified image at: + +http://example.com/files/imagecache/product_thumbnail/files/green-widget... + +NOTE: Each role that wishes to view the images generated by a +particular preset must be given permission on the admin/user/permissions +page. + +ImageCache also defines a theme function that you can use in your modules and +themes to automatically display a manipulated image. For example, to use the +theme function in a .tpl.php file, add the following line where you would like +the image to appear: + + + +Change 'preset_namespace' to the name of your imagecache preset and make sure +that $image_filepath or some other variable contains the actual filepath to +the image you would like to display. + +$alt, $title and $attributes are optional parameters that specify ALT/TITLE +text for the image element in the HTML or other attributes as specified in the +$attributes array. + +Using ImageCache with Contributed Modules: + +ImageCache presets can be put to use in various other modules. For example, when +using CCK with the Imagefield module, you can use the "Display fields" local +task tab to choose a preset to apply to images in that field. Similarly, you +can specify a preset when displaying images attached to nodes using Imagefield +in a View through the Views UI. + +For more information, refer to http://drupal.org/node/163561. + +(Images, page names, and form field names may refer to previous versions of +ImageCache, but the concepts are the same.) diff --git a/sites/all/modules/imagecache/UPGRADE.txt b/sites/all/modules/imagecache/UPGRADE.txt new file mode 100644 index 0000000..1450c7e --- /dev/null +++ b/sites/all/modules/imagecache/UPGRADE.txt @@ -0,0 +1,7 @@ +Upgrading From imagecache version 1.x to 2.0 + -- Install ImageAPI before attempting to upgrade to imagecache 2.x. Your site will 'Blow UP' otherwise. + + -- The old ImageScale actions should be turned into Deprecated Scale actions. You + should update your presets to use the new scale or 'scale and crop' actions. + + diff --git a/sites/all/modules/imagecache/imagecache.api.php b/sites/all/modules/imagecache/imagecache.api.php new file mode 100644 index 0000000..bc967ea --- /dev/null +++ b/sites/all/modules/imagecache/imagecache.api.php @@ -0,0 +1,102 @@ + array( + 'name' => 'Resize', + 'description' => 'Resize an image to an exact set of dimensions, ignoring aspect ratio.', + ), + ); +} + +/** + * Provides default ImageCache presets that can be overridden by site + * administrators. + * + * @return array + * An array of imagecache preset definitions. Each definition can be + * generated by exporting a preset from the database. Each preset + * definition should be keyed on its presetname (for easier interaction + * with drupal_alter) and have the following attributes: + * "presetname": the imagecache preset name. Required. + * "actions": an array of action defintions for this preset. Required. + */ +function hook_imagecache_default_presets() { + $presets = array(); + $presets['thumbnail'] = array ( + 'presetname' => 'thumbnail', + 'actions' => array ( + 0 => array ( + 'weight' => '0', + 'module' => 'imagecache', + 'action' => 'imagecache_scale_and_crop', + 'data' => array ( + 'width' => '60', + 'height' => '60', + ), + ), + ), + ); + return $presets; +} + +/** + * Allows other modules to perform actions on an image before it is flushed. + * + * This hook can be used to send purge requests to a reverse proxy or delete + * a file from a remote file server or CDN when the imagecached version is + * flushed. + * + * Implementations of hook_imagecache_image_flush should not delete the image + * at $filepath, as ImageCache will perform this action. + * + * @param $derivative_path + * The path to the file about to be flushed. + * @param $preset + * An ImageCache preset array. + * @param $original_path + * The Drupal file path to the original image. + */ +function hook_imagecache_image_flush($derivative_path, $preset, $original_path) { +} + +/** + * Allows other modules to perform actions when a preset is about to be flushed. + * + * This hook can be used to send purge requests to a reverse proxy or delete + * files from a remote file server or CDN when the imagecached versions are + * flushed. + * + * Implementations of hook_imagecache_preset_flush should not delete the + * images in $presetdir, as ImageCache will perform this action. + * + * @param $presetdir + * The directory containing the images about to be flushed. + * @param $preset + * An ImageCache preset array. + */ +function hook_imagecache_preset_flush($presetdir, $preset) { +} diff --git a/sites/all/modules/imagecache/imagecache.drush.inc b/sites/all/modules/imagecache/imagecache.drush.inc new file mode 100644 index 0000000..63dff6c --- /dev/null +++ b/sites/all/modules/imagecache/imagecache.drush.inc @@ -0,0 +1,169 @@ + 'imagecache_drush_preset_flush', + 'description' => dt('Flush an imagecache preset.'), + 'examples' => array( + 'drush imagecache-flush foobar' => dt('Flush the ImageCache preset "foobar".'), + ), + 'aliases' => array('icf'), + ); + + $items['imagecache-build'] = array( + 'callback' => 'imagecache_drush_preset_build', + 'description' => dt('Build imagecache derivates for all images for a given preset.'), + 'examples' => array( + 'drush imagecache-build foobar' => dt('Build all images for preset "foobar".'), + ), + 'aliases' => array('icb'), + ); + + return $items; +} + +/** + * Implementation of hook_drush_help(). + */ +function imagecache_drush_help($section) { + switch ($section) { + case 'drush:imagecache-flush': + return dt('Flush a given preset.'); + case 'drush:imagecache-build': + return dt('Build derivative images for a given preset.'); + } +} + +/** + * Drush callback to perform actual imagecache preset flush. + */ +function imagecache_drush_preset_flush() { + $args = func_get_args(); + + // Rebuild imagecache presets. + foreach (imagecache_presets(TRUE) as $preset) { + $preset_names[] = $preset['presetname']; + } + + if (empty($args)) { + $choice = drush_choice($preset_names, 'Enter a number to choose which preset to flush.'); + if ($choice !== FALSE) { + $args[] = $preset_names[$choice]; + } + } + else { + // Implement 'all' + if (count($args) == 1 && $args[0] == 'all') { + $args = $preset_names; + } + } + + // Remove any invalid preset names and report them as errors. + $not_found = array_diff($args, $preset_names); + $args = array_intersect($args, $preset_names); + if ($not_found) { + drush_log(dt('Preset(s) not found: @presets', array('@presets' => implode($not_found, ' '))), 'error'); + } + + if (empty($args)) { + return FALSE; + } + + $path = drush_get_context('DRUSH_DRUPAL_ROOT') .'/'. file_directory_path() .'/imagecache/'; + foreach ($args as $arg) { + // Load preset. + if ($preset = imagecache_preset_by_name($arg)) { + // This mimics the logic inside of the function + // imagecache_preset_flush(), but without the access check. + $presetdir = $path . $preset['presetname']; + if (is_dir($presetdir)) { + _imagecache_recursive_delete($presetdir); + drush_log(dt('Flushed "@preset" preset.', array('@preset' => $arg)), 'ok'); + } + else { + drush_log(dt('Cache for preset "@preset" was already empty.', array('@preset' => $arg)), 'ok'); + } + } + } + return TRUE; +} + +/** + * Drush callback to perform actual imagecache preset build. + */ +function imagecache_drush_preset_build() { + $args = func_get_args(); + + // Rebuild imagecache presets. + foreach (imagecache_presets(TRUE) as $preset) { + $preset_names[] = $preset['presetname']; + } + + if (empty($args)) { + $choice = drush_choice($preset_names, 'Enter a number to choose which preset to flush.'); + if ($choice !== FALSE) { + $args[] = $preset_names[$choice]; + } + } + elseif ($args[0] == 'all') { + // Implement 'all' + $args = $preset_names; + } + + // Remove any invalid preset names and report them as errors. + $not_found = array_diff($args, $preset_names); + $args = array_intersect($args, $preset_names); + if ($not_found) { + drush_log(dt('Preset(s) not found: @presets', array('@presets' => implode($not_found, ' '))), 'error'); + } + + if (empty($args)) { + return FALSE; + } + + // Get a list of files to processes. + $file_query = db_query("SELECT filepath FROM {files} where filemime LIKE 'image%' ORDER BY fid DESC"); + $files = array(); + drush_log(dt('Generating file list...', array()), 'ok'); + while ($filepath = db_result($file_query)) { + if (file_exists($filepath)) { + $files[] = $filepath; + } + } + if (empty($files)) { + drush_log(dt('No images found in the files table.', array()), 'error'); + return FALSE; + } + $count = count($files); + drush_log(dt('Done. @count files to process using these presets: @presets', array('@count' => $count, '@presets' => implode(' ', $args))), 'ok'); + + // Generate the images. + $counter = 0; + $mod = round($count / 200); + foreach ($files as $filepath) { + foreach ($args as $arg) { + $path = imagecache_create_path($arg, $filepath); + if (!file_exists($path)) { + imagecache_generate_image($arg, $filepath); + if (file_exists($path)) { + drush_log(dt('File "@file" created.', array('@file' => $path)), 'ok'); + } + else { + drush_log(dt('File "@file" not created.', array('@file' => $path)), 'error'); + } + } + } + // Output progress. + $counter++; + if ($counter % $mod == 0) { + drush_log(dt('@percent% done.', array('@percent' => round($counter / $count * 100, 2))), 'ok'); + } + } + + return TRUE; +} diff --git a/sites/all/modules/imagecache/imagecache.info b/sites/all/modules/imagecache/imagecache.info new file mode 100644 index 0000000..d1375cc --- /dev/null +++ b/sites/all/modules/imagecache/imagecache.info @@ -0,0 +1,12 @@ +name = ImageCache +description = Dynamic image manipulator and cache. +package = ImageCache +dependencies[] = imageapi +core = 6.x + +; Information added by drupal.org packaging script on 2012-05-23 +version = "6.x-2.0-rc1" +core = "6.x" +project = "imagecache" +datestamp = "1337742655" + diff --git a/sites/all/modules/imagecache/imagecache.install b/sites/all/modules/imagecache/imagecache.install new file mode 100644 index 0000000..5a331e8 --- /dev/null +++ b/sites/all/modules/imagecache/imagecache.install @@ -0,0 +1,322 @@ + $t('ImageCache Directory'), + 'value' => $t('%p is not a directory or is not readable by the webserver.', array('%p' => $imagecache_directory)), + 'severity' => REQUIREMENT_ERROR, + ); + } + elseif (!is_writable($imagecache_directory)) { + $requirements['imagecache_directory'] = array( + 'title' => $t('ImageCache Directory'), + 'value' => $t('%p is not writeable by the webserver.', array('%p' => $imagecache_directory)), + 'severity' => REQUIREMENT_ERROR, + ); + } + else { + $requirements['imagecache_directory'] = array( + 'title' => $t('ImageCache Directory'), + 'value' => $t('An unknown error occured.'), + 'description' => $t('An unknown error occured trying to verify %p is a directory and is writable.', array('%p' => $imagecache_directory)), + 'severity' => REQUIREMENT_ERROR, + ); + } + } + + if (!is_writable(file_directory_temp())) { + $requirements['imagecache_directory'] = array( + 'title' => $t('ImageCache Temp Directory'), + 'value' => $t('%p is not writeable by the webserver.', array('%p' => file_directory_temp())), + 'severity' => REQUIREMENT_ERROR, + ); + } + } + return $requirements; +} + +function imagecache_schema() { + $schema['imagecache_preset'] = array( + 'fields' => array( + 'presetid' => array( + 'description' => t('The primary identifier for an imagecache_preset.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'presetname' => array( + 'description' => t('The primary identifier for a node.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + ), + 'primary key' => array('presetid'), + ); + + $schema['imagecache_action'] = array( + 'fields' => array( + 'actionid' => array( + 'description' => t('The primary identifier for an imagecache_action.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'presetid' => array( + 'description' => t('The primary identifier for an imagecache_preset.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'weight' => array( + 'description' => t('The weight of the action in the preset.'), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'module' => array( + 'description' => t('The module that defined the action.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'action' => array( + 'description' => t('The unique ID of the action to be executed.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'data' => array( + 'description' => t('The configuration data for the action.'), + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('actionid'), + 'indexes' => array( + 'presetid' => array('presetid'), + ), + ); + + return $schema; +} + +/** + * Implementation of hook_install(). + */ +function imagecache_install() { + drupal_install_schema('imagecache'); +} + +/** + * Implementation of hook_uninstall(). + */ +function imagecache_uninstall() { + // Remove any cached images. + $path = file_directory_path() .'/imagecache/'; + if (is_dir($path)) { + _imagecache_recursive_delete($path); + } + + drupal_uninstall_schema('imagecache'); +} + +// Add action id to actions table. +function imagecache_update_1() { + $ret = array(); + $ret[] = update_sql('ALTER TABLE {imagecache_actions} ADD COLUMN actionid INT UNSIGNED NOT NULL primary key auto_increment'); + return $ret; +} + +// Rename rulesets to presets; Make all table names singular; +function imagecache_update_2() { + $ret = array(); + $ret[] = update_sql('ALTER TABLE {imagecache_rulesets} RENAME TO {imagecache_preset}'); + $ret[] = update_sql('ALTER TABLE {imagecache_actions} RENAME TO {imagecache_action}'); + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + $ret[] = update_sql('ALTER TABLE {imagecache_preset} CHANGE rulesetid presetid INT UNSIGNED NOT NULL AUTO_INCREMENT'); + $ret[] = update_sql('ALTER TABLE {imagecache_preset} CHANGE rulesetname presetname VARCHAR(255) NOT NULL DEFAULT \'\''); + $ret[] = update_sql('ALTER TABLE {imagecache_action} CHANGE rulesetid presetid INTEGER NOT NULL DEFAULT 0'); + break; + + case 'pgsql': + $ret[] = update_sql('ALTER TABLE {imagecache_preset} RENAME COLUMN rulesetid TO presetid'); + $ret[] = update_sql('ALTER TABLE {imagecache_preset} RENAME COLUMN rulesetname TO presetname'); + $ret[] = update_sql('ALTER TABLE {imagecache_action} RENAME COLUMN rulesetid TO presetid'); + break; + } + return $ret; +} + + +/** + * Remove auto-increment from tables, instead depending on the sequences table and db_next_id() + */ +function imagecache_update_3() { + $ret = array(); + + $count_action = db_result(db_query('SELECT max(actionid) FROM {imagecache_action}')) + 1; + $count_preset = db_result(db_query('SELECT max(presetid) FROM {imagecache_preset}')) + 1; + + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + $ret[] = update_sql("ALTER TABLE {imagecache_action} CHANGE actionid actionid INT UNSIGNED NOT NULL"); + $ret[] = update_sql("ALTER TABLE {imagecache_preset} CHANGE presetid presetid INT UNSIGNED NOT NULL"); + // Add the sequences + $ret[] = update_sql("INSERT INTO {sequences} (name, id) VALUES ('{imagecache_action}_actionid', $count_action)"); + $ret[] = update_sql("INSERT INTO {sequences} (name, id) VALUES ('{imagecache_preset}_presetid', $count_preset)"); + break; + case 'pgsql': + db_change_column($ret, 'imagecache_action', 'actionid', 'actionid', 'INT', $attributes = array('not null' => TRUE, 'default' => '0')); + db_change_column($ret, 'imagecache_preset', 'presetid', 'presetid', 'INT', $attributes = array('not null' => TRUE, 'default' => '0')); + // Re-add our indexes + $ret[] = update_sql("ALTER TABLE {imagecache_action} ADD PRIMARY KEY (actionid)"); + $ret[] = update_sql("ALTER TABLE {imagecache_preset} ADD PRIMARY KEY (rulesetid)"); + // Add the sequences + $ret[] = update_sql("CREATE SEQUENCE {imagecache_action}_actionid_seq INCREMENT 1 START $count_action;"); + $ret[] = update_sql("CREATE SEQUENCE {imagecache_preset}_presetid_seq INCREMENT 1 START $count_preset;"); + } + return $ret; +} + +function imagecache_update_4() { + $ret = array(); + + // add action column to the imagecache_action table just becuase serialization bugs me. + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + $ret[] = update_sql("ALTER TABLE {imagecache_action} ADD COLUMN action varchar(255) not null default '' after weight"); + break; + case 'pgsql': + $ret[] = update_sql("ALTER TABLE {imagecache_action} ADD COLUMN action varchar(255) NOT NULL DEFAULT ''"); + break; + } + + // unserialize what we can. + $result = db_query("SELECT * FROM {imagecache_action}"); + while ($row = db_fetch_array($result)) { + $data = unserialize($row['data']); + + // remove function from data if present; + $function = $data['function']; + unset($data['function']); + $data = serialize($data); + + // Rename scale and crop for any people who upgraded early... + if ($function == 'scale and crop') { + $function = 'scale_and_crop'; + } + // Keep scale and crop and the old scale function seperate... I don't really want to break BC with + // the 2.x update. We'll deprecate this version. + if ($function == 'scale') { + $function = 'deprecated_scale'; + } + + // prefix with module name as per new status quo. + // since other modules couldn't implement actions before this update + // we assume imagecache... + $function = 'imagecache_'. $function; + + db_query("UPDATE {imagecache_action} SET action='%s', data='%s' WHERE actionid = %d", $function, $data, $row['actionid']); + } + cache_clear_all('*', 'cache', TRUE); + return $ret; +} + +function imagecache_update_5() { + // enable image API. + module_rebuild_cache(); // make sure new modules are in the system table. + module_enable(array('imageapi', 'imageapi_gd', 'imageapi_imagemagick')); // enable our new module. + + // @todo: update formatter names: http://cvs.drupal.org/viewvc.py/drupal/contributions/modules/imagecache/imagecache.module?r1=1.68&r2=1.68.2.8&pathrev=DRUPAL-5--2 + // ln: 516 diff 511. + + return array(); +} + +/** + * Upgrade from Drupal 5 => Drupal 6. + * + * Use serial data type for primary keys. Add module field and presetid index. + */ +function imagecache_update_6000() { + $ret = array(); + + // Our additions to the schema. + $schema['imagecache_preset'] = array( + 'fields' => array( + 'presetid' => array( + 'description' => t('The primary identifier for an imagecache_preset.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + ), + 'primary key' => array('presetid'), + ); + $schema['imagecache_action'] = array( + 'fields' => array( + 'actionid' => array( + 'description' => t('The primary identifier for an imagecache_action.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'module' => array( + 'description' => t('The module that defined the action.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'initial' => 'imagecache', + ), + ), + 'primary key' => array('actionid'), + ); + + // Update primary keys to serial type for Drupal 6 + foreach ($schema as $table => $info) { + $field = $info['primary key'][0]; + if (db_table_exists('sequences')) { + $ret[] = update_sql("DELETE FROM {sequences} WHERE name = '{{$table}}_{$field}'"); + } + db_change_field($ret, $table, $field, $field, $info['fields'][$field]); + } + + // Going to assume that if the table doesn't have a module column that + // it needs the index as well. + if (!db_column_exists('imagecache_action', 'module')) { + // Add 'module' column to action table. + db_add_field($ret, 'imagecache_action', 'module', $schema['imagecache_action']['fields']['module']); + + // Add 'presetid' index to action table + db_add_index($ret, 'imagecache_action', 'presetid', array('presetid')); + } + + + return $ret; +} + +/** + * Make sure the schemas match, the weight should be signed. + */ +function imagecache_update_6001() { + $ret = array(); + db_change_field($ret, 'imagecache_action', 'weight', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0)); + return $ret; +} diff --git a/sites/all/modules/imagecache/imagecache.js b/sites/all/modules/imagecache/imagecache.js new file mode 100644 index 0000000..29b207b --- /dev/null +++ b/sites/all/modules/imagecache/imagecache.js @@ -0,0 +1,50 @@ +/*global Drupal,jQuery */ + +(function($) { + "use strict"; + + Drupal.imagecache = { + /** + * Remove the files directory prefix from a path. + */ + stripFileDirectory: function(path) { + var filePath = Drupal.settings.imagecache.filesDirectory; + if (path.substr(0, filePath.length + 1) === filePath + '/') { + path = path.substr(filePath.length + 1); + } + return path; + }, + + createUrl: function(preset, path) { + var stripped = this.stripFileDirectory(path); + // If the preset is invalid, return the path to the original image. + if ($.inArray(preset, Drupal.settings.imagecache.presets) !== -1) { + return Drupal.settings.imagecache.filesUrl + '/imagecache/' + preset + '/' + stripped; + } + return Drupal.settings.imagecache.filesUrl + '/' + stripped; + } + }; + + Drupal.theme.prototype.imagecache = function(preset, path, alt, title, attributes) { + var image; + image = new Image(); + + image.onload = function() { + $(this).attr({ + width: $(image).width(), + height: $(image).height() + }); + }; + + image.src = Drupal.imagecache.createUrl(preset, path); + image = $.extend(image, { + title: title, + alt: alt + }, attributes); + + return image; + }; +})(jQuery); + +/*jslint browser: true, onevar: true, undef: true */ + diff --git a/sites/all/modules/imagecache/imagecache.module b/sites/all/modules/imagecache/imagecache.module new file mode 100644 index 0000000..55d48ce --- /dev/null +++ b/sites/all/modules/imagecache/imagecache.module @@ -0,0 +1,1367 @@ +/path/to/file.ext. + * + * Presets can be managed at http://example.com/admin/build/imagecache. + * + * To view a derivative image you request a special url containing + * 'imagecache//path/to/file.ext. + * + * If you had a preset names 'thumbnail' and you wanted to see the + * thumbnail version of http://example.com/files/path/to/myimage.jpg you + * would use http://example.com/files/imagecache/thumbnail/path/to/myimage.jpg + * + * ImageCache provides formatters for CCK Imagefields and is leveraged by several + * other modules. ImageCache also relies heavily on ImageAPI for it's image processing. + * If there are errors with actual image processing look to ImageAPI first. + * + * @todo: add watermarking capabilities. + * + */ + +/** + * Imagecache preset storage constant for user-defined presets in the DB. + */ +define('IMAGECACHE_STORAGE_NORMAL', 0); + +/** + * Imagecache preset storage constant for module-defined presets in code. + */ +define('IMAGECACHE_STORAGE_DEFAULT', 1); + +/** + * Imagecache preset storage constant for user-defined presets that override + * module-defined presets. + */ +define('IMAGECACHE_STORAGE_OVERRIDE', 2); + +/********************************************************************************************* + * Drupal Hooks + *********************************************************************************************/ + +/** + * Implementation of hook_perm(). + */ +function imagecache_perm() { + $perms = array('administer imagecache', 'flush imagecache'); + foreach (imagecache_presets() as $preset) { + $perms[] = 'view imagecache '. $preset['presetname']; + } + return $perms; +} + +/** + * Implementation of hook_menu(). + */ +function imagecache_menu() { + $items = array(); + + // standard imagecache callback. + $items[file_directory_path() .'/imagecache'] = array( + 'page callback' => 'imagecache_cache', + 'access callback' => '_imagecache_menu_access_public_files', + 'type' => MENU_CALLBACK + ); + // private downloads imagecache callback + $items['system/files/imagecache'] = array( + 'page callback' => 'imagecache_cache_private', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK + ); + + return $items; +} + +/** + * Menu access callback for public file transfers. + */ +function _imagecache_menu_access_public_files() { + return (FILE_DOWNLOADS_PUBLIC == variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)); +} + +/** + * Implementation of hook_form_FORM_ID_alter. + * + * Clear imagecache presets cache on admin/build/modules form. + */ +function imagecache_form_system_modules_alter(&$form, $form_state) { + imagecache_presets(TRUE); +} + +/** + * Implementation of hook_form_FORM_ID_alter. + * + * The file system form is modified to include an extra submit handler, so + * that imagecache can rebuild the menu after the filesystem path is changed. + */ +function imagecache_form_system_file_system_settings_alter(&$form, &$form_state) { + $form['#submit'][] = 'imagecache_system_file_system_submit'; +} + +/** + * Rebuild menus to ensure we've got the right files directory callback. + */ +function imagecache_system_file_system_submit($form, &$form_state) { + menu_rebuild(); +} + + +/** + * Implementation of hook_theme(). + */ +function imagecache_theme() { + $theme = array( + 'imagecache' => array( + 'arguments' => array( + 'namespace' => NULL, + 'path' => NULL, + 'alt' => NULL, + 'title' => NULL, + )), + 'imagecache_imagelink' => array( + 'arguments' => array( + 'namespace' => NULL, + 'path' => NULL, + 'alt' => NULL, + 'title' => NULL, + 'attributes' => array(), + )), + 'imagecache_resize' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_scale' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_scale_and_crop' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_deprecated_scale' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_crop' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_desaturate' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_rotate' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + 'imagecache_sharpen' => array( + 'file' => 'imagecache_actions.inc', + 'arguments' => array('element' => NULL), + ), + ); + + foreach (imagecache_presets() as $preset) { + $theme['imagecache_formatter_'. $preset['presetname'] .'_default'] = array( + 'arguments' => array('element' => NULL), + 'function' => 'theme_imagecache_formatter_default', + ); + $theme['imagecache_formatter_'. $preset['presetname'] .'_linked'] = array( + 'arguments' => array('element' => NULL), + 'function' => 'theme_imagecache_formatter_linked', + ); + $theme['imagecache_formatter_'. $preset['presetname'] .'_imagelink'] = array( + 'arguments' => array('element' => NULL), + 'function' => 'theme_imagecache_formatter_imagelink', + ); + $theme['imagecache_formatter_'. $preset['presetname'] .'_path'] = array( + 'arguments' => array('element' => NULL), + 'function' => 'theme_imagecache_formatter_path', + ); + $theme['imagecache_formatter_'. $preset['presetname'] .'_url'] = array( + 'arguments' => array('element' => NULL), + 'function' => 'theme_imagecache_formatter_url', + ); + } + + return $theme; + +} + +/** + * Implementation of hook_imagecache_actions. + * + * @return array + * An array of information on the actions implemented by a module. The array + * contains a sub-array for each action node type, with the machine-readable + * action name as the key. Each sub-array has up to 3 attributes. Possible + * attributes: + * + * "name": the human-readable name of the action. Required. + * "description": a brief description of the action. Required. + * "file": the name of the include file the action can be found + * in relative to the implementing module's path. + */ +function imagecache_imagecache_actions() { + $actions = array( + 'imagecache_resize' => array( + 'name' => 'Resize', + 'description' => 'Resize an image to an exact set of dimensions, ignoring aspect ratio.', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_scale' => array( + 'name' => 'Scale', + 'description' => 'Resize an image maintaining the original aspect-ratio (only one value necessary).', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_deprecated_scale' => array( + 'name' => 'Deprecated Scale', + 'description' => 'Precursor to Scale and Crop. Has inside and outside dimension support. This action will be removed in ImageCache 2.1).', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_scale_and_crop' => array( + 'name' => 'Scale And Crop', + 'description' => 'Resize an image while maintaining aspect ratio, then crop it to the specified dimensions.', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_crop' => array( + 'name' => 'Crop', + 'description' => 'Crop an image to the rectangle specified by the given offsets and dimensions.', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_desaturate' => array( + 'name' => 'Desaturate', + 'description' => 'Convert an image to grey scale.', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_rotate' => array( + 'name' => 'Rotate', + 'description' => 'Rotate an image.', + 'file' => 'imagecache_actions.inc', + ), + 'imagecache_sharpen' => array( + 'name' => 'Sharpen', + 'description' => 'Sharpen an image using unsharp masking.', + 'file' => 'imagecache_actions.inc', + ), + ); + + return $actions; +} + +/** + * Pull in actions exposed by other modules using hook_imagecache_actions(). + * + * @param $reset + * Boolean flag indicating whether the cached data should be + * wiped and recalculated. + * + * @return + * An array of actions to be used when transforming images. + */ +function imagecache_action_definitions($reset = FALSE) { + static $actions; + if (!isset($actions) || $reset) { + if (!$reset && ($cache = cache_get('imagecache_actions')) && !empty($cache->data)) { + $actions = $cache->data; + } + else { + foreach (module_implements('imagecache_actions') as $module) { + foreach (module_invoke($module, 'imagecache_actions') as $key => $action) { + $action['module'] = $module; + if (!empty($action['file'])) { + $action['file'] = drupal_get_path('module', $action['module']) .'/'. $action['file']; + } + $actions[$key] = $action; + }; + } + uasort($actions, '_imagecache_definitions_sort'); + cache_set('imagecache_actions', $actions); + } + } + return $actions; +} + +function _imagecache_definitions_sort($a, $b) { + $a = $a['name']; + $b = $b['name']; + if ($a == $b) { + return 0; + } + return ($a < $b) ? -1 : 1; +} + +function imagecache_action_definition($action) { + static $definition_cache; + if (!isset($definition_cache[$action])) { + $definitions = imagecache_action_definitions(); + $definition = (isset($definitions[$action])) ? $definitions[$action] : array(); + + if (isset($definition['file'])) { + require_once($definition['file']); + } + $definition_cache[$action] = $definition; + } + return $definition_cache[$action]; +} + +/** + * Return a URL that points to the location of a derivative of the original + * image transformed with the given preset. + * + * Special care is taken to make this work with the possible combinations of + * Clean URLs and public/private downloads. For example, when Clean URLs are not + * available an URL with query should be returned, like + * http://example.com/?q=files/imagecache/foo.jpg, so that ImageCache is able + * intercept the request for this file. + * + * This code began similarly to Drupal core's function file_create_url(), but + * handles the case of Clean URLs and public downloads differently however. + * It also implements hook_file_url_alter() which was added to Drupal 7 and + * backported to PressFlow 6.x. + * + * @param $presetname + * String specifying an ImageCache preset name. + * @param $filepath + * String specifying the path to the image file. + * @param $bypass_browser_cache + * A Boolean indicating that the URL for the image should be distinct so that + * the visitors browser will not be able to use a previously cached version. + * Defaults to FALSE. + * @param $absolute + * A Boolean indicating that the URL should be absolute. Defaults to TRUE. + */ +function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = FALSE, $absolute = TRUE) { + $args = array( + 'query' => empty($bypass_browser_cache) ? NULL : time(), + // Little hack to avoid having language_url_rewrite() prefix the path with the + // language code, but preserve the domain rewriting. + 'language' => (object) array('language' => '', 'domain' => $GLOBALS['language']->domain), + ); + $file_directory = file_directory_path(); + + // Determine the path of the derivative inside the files directory. + $derivative_path = 'imagecache/'. $presetname .'/'. _imagecache_strip_file_directory($filepath); + + // Then construct a full path and see if anyone wants to alter it. + $altered_path = $old_path = $file_directory .'/'. $derivative_path; + drupal_alter('file_url', $altered_path); + + // If any module has altered the path, then return the alteration... + if ($altered_path != $old_path) { + // ...but use url() so our $bypass_browser_cache parameter is honored. + return url($altered_path, $args); + } + + // It was unchanged so use the download method's prefix. + $prefix = array( + FILE_DOWNLOADS_PUBLIC => $file_directory, + FILE_DOWNLOADS_PRIVATE => 'system/files', + ); + $path = $prefix[variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)] .'/'. $derivative_path; + + return url($path, $args + array('absolute' => $absolute)); +} + +/** + * Return a file system location that points to the location of a derivative + * of the original image at @p $path, transformed with the given @p $preset. + * Keep in mind that the image might not yet exist and won't be created. + */ +function imagecache_create_path($presetname, $path) { + $path = _imagecache_strip_file_directory($path); + return file_create_path() .'/imagecache/'. $presetname .'/'. $path; +} + +/** + * Remove a possible leading file directory path from the given path. + */ +function _imagecache_strip_file_directory($path) { + $dirpath = file_directory_path(); + $dirlen = strlen($dirpath); + if (substr($path, 0, $dirlen + 1) == $dirpath .'/') { + $path = substr($path, $dirlen + 1); + } + return $path; +} + + +/** + * callback for handling public files imagecache requests. + */ +function imagecache_cache() { + $args = func_get_args(); + $preset = check_plain(array_shift($args)); + $path = implode('/', $args); + _imagecache_cache($preset, $path); +} + +/** + * callback for handling private files imagecache requests + */ +function imagecache_cache_private() { + $args = func_get_args(); + $preset = check_plain(array_shift($args)); + $source = implode('/', $args); + + if (user_access('view imagecache '. $preset) && !in_array(-1, module_invoke_all('file_download', $source))) { + _imagecache_cache($preset, $source); + } + else { + // if there is a 403 image, display it. + $accesspath = file_create_path('imagecache/'. $preset .'.403.png'); + if (is_file($accesspath)) { + imagecache_transfer($accesspath); + exit; + } + header('HTTP/1.0 403 Forbidden'); + exit; + } +} + +/** + * Handle request validation and responses to ImageCache requests. + * + * @see imagecache_generate_image() if you're writing code that needs to have + * ImageCache generate images but not send them to a browser. + */ +function _imagecache_cache($presetname, $path) { + if (!$preset = imagecache_preset_by_name($presetname)) { + // Send a 404 if we don't know of a preset. + header("HTTP/1.0 404 Not Found"); + exit; + } + + // umm yeah deliver it early if it is there. especially useful + // to prevent lock files from being created when delivering private files. + $dst = imagecache_create_path($preset['presetname'], $path); + if (is_file($dst)) { + imagecache_transfer($dst); + } + + // preserve path for watchdog. + $src = $path; + + // Check if the path to the file exists. + if (!is_file($src) && !is_file($src = file_create_path($src))) { + watchdog('imagecache', '404: Unable to find %image ', array('%image' => $src), WATCHDOG_ERROR); + header("HTTP/1.0 404 Not Found"); + exit; + }; + + // Bail if the requested file isn't an image you can't request .php files + // etc... + if (!getimagesize($src)) { + watchdog('imagecache', '403: File is not an image %image ', array('%image' => $src), WATCHDOG_ERROR); + header('HTTP/1.0 403 Forbidden'); + exit; + } + + $lockfile = file_directory_temp() .'/'. $preset['presetname'] . basename($src); + if (file_exists($lockfile)) { + watchdog('imagecache', 'ImageCache already generating: %dst, Lock file: %tmp.', array('%dst' => $dst, '%tmp' => $lockfile), WATCHDOG_NOTICE); + // 307 Temporary Redirect, to myself. Lets hope the image is done next time around. + header('Location: '. request_uri(), TRUE, 307); + exit; + } + touch($lockfile); + // register the shtdown function to clean up lock files. by the time shutdown + // functions are being called the cwd has changed from document root, to + // server root so absolute paths must be used for files in shutdown functions. + register_shutdown_function('file_delete', realpath($lockfile)); + + // check if deriv exists... (file was created between apaches request handler and reaching this code) + // otherwise try to create the derivative. + if (file_exists($dst) || imagecache_build_derivative($preset['actions'], $src, $dst)) { + imagecache_transfer($dst); + } + // Generate an error if image could not generate. + watchdog('imagecache', 'Failed generating an image from %image using imagecache preset %preset.', array('%image' => $path, '%preset' => $preset['presetname']), WATCHDOG_ERROR); + header("HTTP/1.0 500 Internal Server Error"); + exit; +} + +/** + * Apply an action to an image. + * + * @param $action + * Action array + * @param $image + * Image object + * @return + * Boolean, TRUE indicating success and FALSE failure. + */ +function _imagecache_apply_action($action, &$image) { + $actions = imagecache_action_definitions(); + + if ($definition = imagecache_action_definition($action['action'])) { + $function = $action['action'] .'_image'; + if (function_exists($function)) { + return $function($image, $action['data']); + } + } + // skip undefined actions.. module probably got uninstalled or disabled. + watchdog('imagecache', 'non-existant action %action', array('%action' => $action['action']), WATCHDOG_NOTICE); + return TRUE; +} + +/** + * Helper function to transfer files from imagecache. + * + * Determines MIME type and sets a last modified header. + * + * @param $path + * String containing the path to file to be transferred. + * @return + * This function does not return. It calls exit(). + */ + +function imagecache_transfer($path) { + $size = getimagesize($path); + $headers = array('Content-Type: '. mime_header_encode($size['mime'])); + + if ($fileinfo = stat($path)) { + $headers[] = 'Content-Length: '. $fileinfo[7]; + $headers[] = 'Expires: ' . gmdate('D, d M Y H:i:s', time() + 1209600) .' GMT'; + $headers[] = 'Cache-Control: max-age=1209600, private, must-revalidate'; + _imagecache_cache_set_cache_headers($fileinfo, $headers); + } + file_transfer($path, $headers); + exit; +} + +/** + * Set file headers that handle "If-Modified-Since" correctly for the + * given fileinfo. + * + * Note that this function may return or may call exit(). + * + * Most code has been taken from drupal_page_cache_header(). + * + * @param $fileinfo + * Array returned by stat(). + * @param + * Array of existing headers. + * @return + * Nothing but beware that this function may not return. + */ +function _imagecache_cache_set_cache_headers($fileinfo, &$headers) { + // Set default values: + $last_modified = gmdate('D, d M Y H:i:s', $fileinfo[9]) .' GMT'; + $etag = '"'. md5($last_modified) .'"'; + + // See if the client has provided the required HTTP headers: + $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) + ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) + : FALSE; + $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) + ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) + : FALSE; + + if ($if_modified_since && $if_none_match + && $if_none_match == $etag // etag must match + && $if_modified_since == $last_modified) { // if-modified-since must match + header('HTTP/1.1 304 Not Modified'); + // All 304 responses must send an etag if the 200 response + // for the same object contained an etag + header('Etag: '. $etag); + // We must also set Last-Modified again, so that we overwrite Drupal's + // default Last-Modified header with the right one + header('Last-Modified: '. $last_modified); + exit; + } + + // Send appropriate response: + $headers[] = 'Last-Modified: '. $last_modified; + $headers[] = 'ETag: '. $etag; +} + +/** + * Create a new image based on an image preset. + * + * @param $preset + * An image preset array. + * @param $source + * Path of the source file. + * @param $destination + * Path of the destination file. + * @return + * TRUE if an image derivative is generated, FALSE if no image + * derivative is generated. NULL if the derivative is being generated. + */ +function imagecache_build_derivative($actions, $src, $dst) { + // get the folder for the final location of this preset... + $dir = dirname($dst); + + // Build the destination folder tree if it doesn't already exists. + if (!file_check_directory($dir, FILE_CREATE_DIRECTORY) && !mkdir($dir, 0775, TRUE)) { + watchdog('imagecache', 'Failed to create imagecache directory: %dir', array('%dir' => $dir), WATCHDOG_ERROR); + return FALSE; + } + + // file_check_directory() has an annoying habit of displaying "directory ... + // has been created" status messages. To avoid confusing visitors we clear + // out all the status messages for non-ImageCache admins. This might affect + // some other messages but errors and warnings should still be displayed. + if (!user_access('administer imagecache')) { + drupal_get_messages('status', TRUE); + } + + // Simply copy the file if there are no actions. + if (empty($actions)) { + return file_copy($src, $dst, FILE_EXISTS_REPLACE); + } + + if (!$image = imageapi_image_open($src)) { + return FALSE; + } + + if (file_exists($dst)) { + watchdog('imagecache', 'Cached image file %dst already exists but is being regenerated. There may be an issue with your rewrite configuration.', array('%dst' => $dst), WATCHDOG_WARNING); + } + + foreach ($actions as $action) { + if (!empty($action['data'])) { + // Make sure the width and height are computed first so they can be used + // in relative x/yoffsets like 'center' or 'bottom'. + if (isset($action['data']['width'])) { + $action['data']['width'] = _imagecache_percent_filter($action['data']['width'], $image->info['width']); + } + if (isset($action['data']['height'])) { + $action['data']['height'] = _imagecache_percent_filter($action['data']['height'], $image->info['height']); + } + if (isset($action['data']['xoffset'])) { + $action['data']['xoffset'] = _imagecache_keyword_filter($action['data']['xoffset'], $image->info['width'], $action['data']['width']); + } + if (isset($action['data']['yoffset'])) { + $action['data']['yoffset'] = _imagecache_keyword_filter($action['data']['yoffset'], $image->info['height'], $action['data']['height']); + } + } + if (!_imagecache_apply_action($action, $image)) { + watchdog('imagecache', 'action(id:%id): %action failed for %src', array('%id' => $action['actionid'], '%action' => $action['action'], '%src' => $src), WATCHDOG_ERROR); + return FALSE; + } + } + + if (!imageapi_image_close($image, $dst)) { + watchdog('imagecache', 'There was an error saving the new image file %dst.', array('%dst' => $dst), WATCHDOG_ERROR); + return FALSE; + } + + return TRUE; +} + +/** + * Implementation of hook_user(). + */ +function imagecache_user($op, &$edit, &$account, $category = NULL) { + // Flush cached old user picture. + if ($op == 'update' && !empty($account->picture)) { + imagecache_image_flush($account->picture); + } +} + +/** + * Implementation of filefield.module's hook_file_delete(). + * + * Remove derivative images after the originals are deleted by filefield. + */ +function imagecache_file_delete($file) { + imagecache_image_flush($file->filepath); +} + +/** + * Implementation of hook_field_formatter_info(). + * + * imagecache formatters are named as $presetname_$style + * $style is used to determine how the preset should be rendered. + * If you are implementing custom imagecache formatters please treat _ as + * reserved. + * + * @todo: move the linking functionality up to imagefield and clean up the default image + * integration. + */ +function imagecache_field_formatter_info() { + $formatters = array(); + foreach (imagecache_presets() as $preset) { + $formatters[$preset['presetname'] .'_default'] = array( + 'label' => t('@preset image', array('@preset' => $preset['presetname'])), + 'field types' => array('image', 'filefield'), + ); + $formatters[$preset['presetname'] .'_linked'] = array( + 'label' => t('@preset image linked to node', array('@preset' => $preset['presetname'])), + 'field types' => array('image', 'filefield'), + ); + $formatters[$preset['presetname'] .'_imagelink'] = array( + 'label' => t('@preset image linked to image', array('@preset' => $preset['presetname'])), + 'field types' => array('image', 'filefield'), + ); + $formatters[$preset['presetname'] .'_path'] = array( + 'label' => t('@preset file path', array('@preset' => $preset['presetname'])), + 'field types' => array('image', 'filefield'), + ); + $formatters[$preset['presetname'] .'_url'] = array( + 'label' => t('@preset URL', array('@preset' => $preset['presetname'])), + 'field types' => array('image', 'filefield'), + ); + } + return $formatters; +} + +function theme_imagecache_formatter_default($element) { + // Inside a view $element may contain NULL data. In that case, just return. + if (empty($element['#item']['fid'])) { + return ''; + } + + // Extract the preset name from the formatter name. + $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); + $style = 'linked'; + $style = 'default'; + + $item = $element['#item']; + $item['data']['alt'] = isset($item['data']['alt']) ? $item['data']['alt'] : ''; + $item['data']['title'] = isset($item['data']['title']) ? $item['data']['title'] : NULL; + + $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}"; + return theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title'], array('class' => $class)); +} + +function theme_imagecache_formatter_linked($element) { + // Inside a view $element may contain NULL data. In that case, just return. + if (empty($element['#item']['fid'])) { + return ''; + } + + // Extract the preset name from the formatter name. + $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); + $style = 'linked'; + + $item = $element['#item']; + $item['data']['alt'] = isset($item['data']['alt']) ? $item['data']['alt'] : ''; + $item['data']['title'] = isset($item['data']['title']) ? $item['data']['title'] : NULL; + + $imagetag = theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title']); + $path = empty($item['nid']) ? '' : 'node/'. $item['nid']; + $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}"; + return l($imagetag, $path, array('attributes' => array('class' => $class), 'html' => TRUE)); +} + +function theme_imagecache_formatter_imagelink($element) { + // Inside a view $element may contain NULL data. In that case, just return. + if (empty($element['#item']['fid'])) { + return ''; + } + + // Extract the preset name from the formatter name. + $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); + $style = 'imagelink'; + + $item = $element['#item']; + $item['data']['alt'] = isset($item['data']['alt']) ? $item['data']['alt'] : ''; + $item['data']['title'] = isset($item['data']['title']) ? $item['data']['title'] : NULL; + + $imagetag = theme('imagecache', $presetname, $item['filepath'], $item['data']['alt'], $item['data']['title']); + $path = file_create_url($item['filepath']); + $class = "imagecache imagecache-$presetname imagecache-$style imagecache-{$element['#formatter']}"; + return l($imagetag, $path, array('attributes' => array('class' => $class), 'html' => TRUE)); +} + +function theme_imagecache_formatter_path($element) { + // Inside a view $element may contain NULL data. In that case, just return. + if (empty($element['#item']['fid'])) { + return ''; + } + + // Extract the preset name from the formatter name. + $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); + + return imagecache_create_path($presetname, $element['#item']['filepath']); +} + +function theme_imagecache_formatter_url($element) { + // Inside a view $element may contain NULL data. In that case, just return. + if (empty($element['#item']['fid'])) { + return ''; + } + + // Extract the preset name from the formatter name. + $presetname = substr($element['#formatter'], 0, strrpos($element['#formatter'], '_')); + + return imagecache_create_url($presetname, $element['#item']['filepath']); +} + +/** + * Accept a percentage and return it in pixels. + */ +function _imagecache_percent_filter($value, $current_pixels) { + if (strpos($value, '%') !== FALSE) { + $value = str_replace('%', '', $value) * 0.01 * $current_pixels; + } + return $value; +} + +/** + * Accept a keyword (center, top, left, etc) and return it as an offset in pixels. + */ +function _imagecache_keyword_filter($value, $current_pixels, $new_pixels) { + switch ($value) { + case 'top': + case 'left': + $value = 0; + break; + case 'bottom': + case 'right': + $value = $current_pixels - $new_pixels; + break; + case 'center': + $value = $current_pixels/2 - $new_pixels/2; + break; + } + return $value; +} + +/** + * Recursively delete all files and folders in the specified filepath, then + * delete the containing folder. + * + * Note that this only deletes visible files with write permission. + * + * @param string $path + * A filepath relative to file_directory_path. + */ +function _imagecache_recursive_delete($path) { + if (is_file($path) || is_link($path)) { + unlink($path); + } + elseif (is_dir($path)) { + $d = dir($path); + while (($entry = $d->read()) !== FALSE) { + if ($entry == '.' || $entry == '..') continue; + $entry_path = $path .'/'. $entry; + _imagecache_recursive_delete($entry_path); + } + $d->close(); + rmdir($path); + } + else { + watchdog('imagecache', 'Unknown file type(%path) stat: %stat ', + array('%path' => $path, '%stat' => print_r(stat($path),1)), WATCHDOG_ERROR); + } + +} + +/** + * Create and image tag for an imagecache derivative + * + * @param $presetname + * String with the name of the preset used to generate the derivative image. + * @param $path + * String path to the original image you wish to create a derivative image + * tag for. + * @param $alt + * Optional string with alternate text for the img element. + * @param $title + * Optional string with title for the img element. + * @param $attributes + * Optional drupal_attributes() array. If $attributes is an array then the + * default imagecache classes will not be set automatically, you must do this + * manually. + * @param $getsize + * If set to TRUE, the image's dimension are fetched and added as width/height + * attributes. + * @param $absolute + * A Boolean indicating that the URL should be absolute. Defaults to TRUE. + * @return + * HTML img element string. + */ +function theme_imagecache($presetname, $path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE, $absolute = TRUE) { + // Check is_null() so people can intentionally pass an empty array of + // to override the defaults completely. + if (is_null($attributes)) { + $attributes = array('class' => 'imagecache imagecache-'. $presetname); + } + $ours = array( + 'src' => imagecache_create_url($presetname, $path, FALSE, $absolute), + 'alt' => $alt, + 'title' => $title, + ); + if ($getsize && ($image = image_get_info(imagecache_create_path($presetname, $path)))) { + $ours += array('width' => $image['width'], 'height' => $image['height']); + } + + return ''; +} + +/** + * Create a link the original image that wraps the derivative image. + * + * @param $presetname + * String with the name of the preset used to generate the derivative image. + * @param $path + * String path to the original image you wish to create a derivative image + * tag for. + * @param $alt + * Optional string with alternate text for the img element. + * @param $title + * Optional string with title for the img element. + * @param attributes + * Optional drupal_attributes() array for the link. + * @return + * An HTML string. + */ +function theme_imagecache_imagelink($presetname, $path, $alt = '', $title = '', $attributes = NULL) { + $image = theme('imagecache', $presetname, $path, $alt, $title); + $original_image_url = file_create_url($path); + return l($image, $original_image_url, array('absolute' => FALSE, 'html' => TRUE, 'attributes' => $attributes)); +} + +/** + * Imagecache JS settings and theme function. + */ +function imagecache_add_js() { + static $added; + if (!$added) { + $added = TRUE; + drupal_add_js(drupal_get_path('module', 'imagecache') .'/imagecache.js'); + + $mode = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC); + if ($mode == FILE_DOWNLOADS_PUBLIC) { + $settings['filesUrl'] = $GLOBALS['base_path'] . file_directory_path(); + } + elseif ($mode == FILE_DOWNLOADS_PRIVATE) { + $settings['filesUrl'] = 'system/files'; + } + $settings['filesDirectory'] = file_directory_path(); + $settings['presets'] = array_keys(imagecache_presets()); + drupal_add_js(array('imagecache' => $settings), 'setting'); + } +} + +/** + * ImageCache 2.x API + * + * The API for imagecache has changed. The 2.x API returns more structured + * data, has shorter function names, and implements more aggressive metadata + * caching. + * + */ + +/** + * Get an array of all presets and their settings. + * + * @param reset + * if set to TRUE it will clear the preset cache + * + * @return + * array of presets array( $preset_id => array('presetid' => integer, 'presetname' => string)) + */ +function imagecache_presets($reset = FALSE) { + static $presets = array(); + + // Clear caches if $reset is TRUE; + if ($reset) { + $presets = array(); + cache_clear_all('imagecache:presets', 'cache'); + + // Clear the content.module cache (refreshes the list of formatters provided by imagefield.module). + if (module_exists('content')) { + content_clear_type_cache(); + } + } + // Return presets if the array is populated. + if (!empty($presets)) { + return $presets; + } + + // Grab from cache or build the array. To ensure that the Drupal 5 upgrade + // path works, we also check whether the presets list is an array. + if (($cache = cache_get('imagecache:presets', 'cache')) && is_array($cache->data)) { + $presets = $cache->data; + } + else { + $normal_presets = array(); + + $result = db_query('SELECT * FROM {imagecache_preset} ORDER BY presetname'); + while ($preset = db_fetch_array($result)) { + $presets[$preset['presetid']] = $preset; + $presets[$preset['presetid']]['actions'] = imagecache_preset_actions($preset); + $presets[$preset['presetid']]['storage'] = IMAGECACHE_STORAGE_NORMAL; + + // Collect normal preset names so we can skip defaults and mark overrides accordingly + $normal_presets[$preset['presetname']] = $preset['presetid']; + } + + // Collect default presets and allow modules to modify them before they + // are cached. + $default_presets = module_invoke_all('imagecache_default_presets'); + drupal_alter('imagecache_default_presets', $default_presets); + + // Add in default presets if they don't conflict with any normal presets. + // Mark normal presets that take the same preset namespace as overrides. + foreach ($default_presets as $preset) { + if (!empty($preset['presetname'])) { + if (!isset($normal_presets[$preset['presetname']])) { + $preset['storage'] = IMAGECACHE_STORAGE_DEFAULT; + // Use a string preset identifier + $preset['presetid'] = $preset['presetname']; + $presets[$preset['presetname']] = $preset; + } + else { + $presetid = $normal_presets[$preset['presetname']]; + $presets[$presetid]['storage'] = IMAGECACHE_STORAGE_OVERRIDE; + } + } + } + + cache_set('imagecache:presets', $presets); + } + return $presets; +} + +/** + * Load a preset by preset_id. + * + * @param preset_id + * The numeric id of a preset. + * + * @return + * preset array( 'presetname' => string, 'presetid' => integet) + * empty array if preset_id is an invalid preset + */ +function imagecache_preset($preset_id, $reset = FALSE) { + $presets = imagecache_presets($reset); + return (isset($presets[$preset_id])) ? $presets[$preset_id] : array(); +} + +/** + * Load a preset by name. + * + * @param preset_name + * + * @return + * preset array( 'presetname' => string, 'presetid' => integer) + * empty array if preset_name is an invalid preset + */ + +function imagecache_preset_by_name($preset_name) { + static $presets_by_name = array(); + if (!$presets_by_name && $presets = imagecache_presets()) { + foreach ($presets as $preset) { + $presets_by_name[$preset['presetname']] = $preset; + } + } + return (isset($presets_by_name[$preset_name])) ? $presets_by_name[$preset_name] : array(); +} + +/** + * Save an ImageCache preset. + * + * @param preset + * an imagecache preset array. + * @return + * a preset array. In the case of a new preset, 'presetid' will be populated. + */ +function imagecache_preset_save($preset) { + // @todo: CRUD level validation? + if (isset($preset['presetid']) && is_numeric($preset['presetid'])) { + drupal_write_record('imagecache_preset', $preset, 'presetid'); + } + else { + drupal_write_record('imagecache_preset', $preset); + } + + // Reset presets cache. + imagecache_preset_flush($preset); + imagecache_presets(TRUE); + + // Rebuild Theme Registry + drupal_rebuild_theme_registry(); + + return $preset; +} + +function imagecache_preset_delete($preset) { + imagecache_preset_flush($preset); + db_query('DELETE FROM {imagecache_action} where presetid = %d', $preset['presetid']); + db_query('DELETE FROM {imagecache_preset} where presetid = %d', $preset['presetid']); + imagecache_presets(TRUE); + return TRUE; +} + +function imagecache_preset_actions($preset, $reset = FALSE) { + static $actions_cache = array(); + + if ($reset || empty($actions_cache[$preset['presetid']])) { + $result = db_query('SELECT * FROM {imagecache_action} where presetid = %d order by weight', $preset['presetid']); + while ($row = db_fetch_array($result)) { + $row['data'] = unserialize($row['data']); + $actions_cache[$preset['presetid']][] = $row; + } + } + + return isset($actions_cache[$preset['presetid']]) ? $actions_cache[$preset['presetid']] : array(); +} + +/** + * Flush cached media for a preset. + * + * @param preset + * an imagecache preset array. + */ +function imagecache_preset_flush($preset) { + if (user_access('flush imagecache')) { + $presetdir = realpath(file_directory_path() .'/imagecache/'. $preset['presetname']); + if (is_dir($presetdir)) { + module_invoke_all('imagecache_preset_flush', $presetdir, $preset); + _imagecache_recursive_delete($presetdir); + } + } +} + +/** + * Clear cached versions of a specific file in all presets. + * @param $path + * The Drupal file path to the original image. + */ +function imagecache_image_flush($path) { + foreach (imagecache_presets() as $preset) { + $derivative_path = imagecache_create_path($preset['presetname'], $path); + module_invoke_all('imagecache_image_flush', $derivative_path, $preset, $path); + file_delete($derivative_path); + } +} + +function imagecache_action($actionid) { + static $actions; + + if (!isset($actions[$actionid])) { + $action = array(); + + $result = db_query('SELECT * FROM {imagecache_action} WHERE actionid=%d', $actionid); + if ($row = db_fetch_array($result)) { + $action = $row; + $action['data'] = unserialize($action['data']); + + $definition = imagecache_action_definition($action['action']); + $action = array_merge($definition, $action); + $actions[$actionid] = $action; + } + } + return $actions[$actionid]; +} + +function imagecache_action_load($actionid) { + return imagecache_action($actionid, TRUE); +} + +function imagecache_action_save($action) { + $definition = imagecache_action_definition($action['action']); + $action = array_merge($definition, $action); + + // Some actions don't have data. Make an empty one to prevent SQL errors. + if (!isset($action['data'])) { + $action['data'] = array(); + } + + if (!empty($action['actionid'])) { + drupal_write_record('imagecache_action', $action, 'actionid'); + } + else { + drupal_write_record('imagecache_action', $action); + } + $preset = imagecache_preset($action['presetid']); + imagecache_preset_flush($preset); + imagecache_presets(TRUE); + return $action; +} + +function imagecache_action_delete($action) { + db_query('DELETE FROM {imagecache_action} WHERE actionid=%d', $action['actionid']); + $preset = imagecache_preset($action['presetid']); + imagecache_preset_flush($preset); + imagecache_presets(TRUE); +} + +/** + * Implementation of hook_action_info(). + * + * Note: These are actions in the Drupal core trigger.module sense, not + * ImageCache actions. + */ +function imagecache_action_info() { + $actions = array(); + + if (module_exists('filefield')) { + $actions['imagecache_flush_action'] = array( + 'type' => 'node', + 'description' => t("ImageCache: Flush ALL presets for this node's filefield images"), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave', 'delete', 'insert', 'update'), + ) + ); + $actions['imagecache_generate_all_action'] = array( + 'type' => 'node', + 'description' => t("ImageCache: Generate ALL presets for this node's filefield images"), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave', 'insert', 'update'), + ) + ); + $actions['imagecache_generate_action'] = array( + 'type' => 'node', + 'description' => t("ImageCache: Generate configured preset(s) for this node's filefield images"), + 'configurable' => TRUE, + 'hooks' => array( + 'nodeapi' => array('presave', 'insert', 'update'), + ) + ); + } + + return $actions; +} + +/** + * Flush all imagecache presets for a given node. + * + * @param $node + * A node object. + * @param $context + * Contains values from the calling action. + * + * @see imagecache_action_info() + */ +function imagecache_flush_action(&$node, $context) { + $files = imagecache_get_images_in_node($node); + if (!empty($files)) { + foreach ($files as $file) { + imagecache_image_flush($file['filepath']); + } + } +} + +/** + * Generate all imagecache presets for the given node. + * + * @param $node + * A node object. + * @param $context + * Contains values from the calling action. + * + * @see imagecache_action_info() + */ +function imagecache_generate_all_action(&$node, $context) { + $files = imagecache_get_images_in_node($node); + $presets = imagecache_presets(); + if (!empty($files) && !empty($presets)) { + foreach ($files as $file) { + foreach ($presets as $presetname) { + imagecache_generate_image($presetname['presetname'], $file['filepath']); + } + } + } +} + +/** + * Generate imagecache presets for the given node and presets. + * + * @param $node + * A node object. + * @param $context + * Contains values from the calling action. + * + * @see imagecache_action_info() + * @see imagecache_generate_action_form() + */ +function imagecache_generate_action(&$node, $context) { + $files = imagecache_get_images_in_node($node); + if (!empty($files) && !empty($context['imagecache_presets'])) { + foreach ($files as $file) { + foreach ($context['imagecache_presets'] as $presetname) { + imagecache_generate_image($presetname, $file['filepath']); + } + } + } +} + +/** + * Form for configuring the generate action. + * + * @see imagecache_generate_action() + */ +function imagecache_generate_action_form($context) { + $options = array(); + foreach (imagecache_presets() as $preset) { + $options[$preset['presetname']] = $preset['presetname']; + } + $form['presets'] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#description' => t('Select which imagecache presets will be effected'), + '#required' => TRUE, + '#default_value' => isset($context['imagecache_presets']) ? $context['imagecache_presets'] : array(), + ); + // Filter out false checkboxes: http://drupal.org/node/61760#comment-402631 + $form['array_filter'] = array('#type' => 'value', '#value' => TRUE); + return $form; +} + +/** + * Generate a derivative image given presetname and filepath. + * + * This is a developer friendly version of _imagecache_cache(), it doesn't worry + * about sending HTTP headers or an image back to the client so it's much + * simpler. + * + * @param $presetname + * ImageCache preset array. + * @param $filepath + * String filepath from the files table. + * @return + * A Boolean indicating if the operation succeeded. + */ +function imagecache_generate_image($presetname, $filepath) { + $preset = imagecache_preset_by_name($presetname); + if (empty($preset['presetname'])) { + return FALSE; + } + $destination = imagecache_create_path($preset['presetname'], $filepath); + if (file_exists($destination)) { + return TRUE; + } + return imagecache_build_derivative($preset['actions'], $filepath, $destination); +} + +/** + * Given a node, get all images associated with it. + * + * Currently this only works with images stored in filefields. + * + * @param $node + * Node object. + * @return + * An array of info from the files table. + */ +function imagecache_get_images_in_node(&$node) { + $files = array(); + if (module_exists('filefield')) { + $data = filefield_get_node_files($node); + foreach ($data as $key => $value) { + if (stristr($value['filemime'], 'image')) { + $files[$key] = $value; + } + } + } + return $files; +} + diff --git a/sites/all/modules/imagecache/imagecache_actions.inc b/sites/all/modules/imagecache/imagecache_actions.inc new file mode 100644 index 0000000..a1c95d7 --- /dev/null +++ b/sites/all/modules/imagecache/imagecache_actions.inc @@ -0,0 +1,340 @@ + 'textfield', + '#title' => t('Width'), + '#default_value' => isset($action['width']) ? $action['width'] : '100%', + '#description' => t('Enter a width in pixels or as a percentage. i.e. 500 or 80%.'), + ); + $form['height'] = array( + '#type' => 'textfield', + '#title' => t('Height'), + '#default_value' => isset($action['height']) ? $action['height'] : '100%', + '#description' => t('Enter a height in pixels or as a percentage. i.e. 500 or 80%.'), + ); + return $form; +} + +function imagecache_resize_image(&$image, $data) { + if (!imageapi_image_resize($image, $data['width'], $data['height'])) { + watchdog('imagecache', 'imagecache_resize_image failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; +} + +function theme_imagecache_resize($element) { + $data = $element['#value']; + if ($data['width'] && $data['height']) { + return check_plain($data['width']) . 'x' . check_plain($data['height']); + } + return ($data['width']) ? t('width @width', array('@width' => $data['width'])) : t('height @height', array('@height' => $data['height'])); +} + +/** + * ImageCache Scale + */ +function imagecache_scale_form($data = array()) { + $form = imagecache_resize_form($data); + $form['upscale'] = array( + '#type' => 'checkbox', + '#default_value' => (isset($data['upscale'])) ? $data['upscale'] : 0, + '#title' => t('Allow Upscaling'), + '#description' => t('Let scale make images larger than their original size'), + ); + return $form; +} + +function theme_imagecache_scale($element) { + return theme_imagecache_resize($element) . ' ' . ($element['#value']['upscale'] ? '(' . t('upscaling allowed') . ')' : ''); +} + +function imagecache_scale_image(&$image, $data) { + // Set impossibly large values if the width and height aren't set. + $data['width'] = $data['width'] ? $data['width'] : 9999999; + $data['height'] = $data['height'] ? $data['height'] : 9999999; + if (!imageapi_image_scale($image, $data['width'], $data['height'], $data['upscale'])) { + watchdog('imagecache', 'imagecache_scale_image failed. image: %image, data: %data.', array('%image' => $image->source, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; +} + + +/** + * ImageCache Scale and Crop + */ +function imagecache_scale_and_crop_form($data = array()) { + return imagecache_resize_form($data); +} + +function theme_imagecache_scale_and_crop($element) { + return theme_imagecache_resize($element); +} + + +function imagecache_scale_and_crop_image(&$image, $data) { + if (!imageapi_image_scale_and_crop($image, $data['width'], $data['height'])) { + watchdog('imagecache', 'imagecache_scale_and_crop failed. image: %image, data: %data.', array('%image' => $image->source, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; +} + + + +/** + * ImageCache Deprecated Scale. + * This will be removed in imagecache 2.1 + */ +function imagecache_deprecated_scale_form($data = array()) { + $helptext = array(); + $helptext['inside'] = t('Inside dimensions: Final dimensions will be less than or equal to the entered width and height. Useful for ensuring a maximum height and/or width.'); + $helptext['outside'] = t('Outside dimensions: Final dimensions will be greater than or equal to the entered width and height. Ideal for cropping the result to a square.'); + $description = '