diff --git a/.gitignore b/.gitignore index e113ca79ddcdfb35a2f265bcfb26db9ed79b3532..137a4a666c8c6e34f7a8c67e1e3fcd9528f21dea 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ sites/default/settings.php .project .settings .settings/* + +/sites/all/themes/explore_center +/resetunlcms.sh +/UNL_CMS.sublime-project +/UNL_CMS.sublime-workspace \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index e3a8f200d8c73a5d3792614af196405e73b3d1de..b5cbeb7c6bf5ed9f13970e5062aa0e48e25a45f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,15 @@ [submodule "vendor/WDN-TinyMCE"] - path = vendor/WDN-TinyMCE - url = git@github.unl.edu:iim/TinyMCE.git + path = vendor/WDN-TinyMCE + url = git@github.unl.edu:iim/TinyMCE.git [submodule "vendor/NmcFramework"] - path = vendor/NmcFramework - url = git@github.unl.edu:UNL-Information-Services/NMC-PHP-Framework.git + path = vendor/NmcFramework + url = git@github.unl.edu:UNL-Information-Services/NMC-PHP-Framework.git +[submodule "sites/all/modules/diff"] + path = sites/all/modules/diff + url = http://git.drupal.org/project/diff.git +[submodule "sites/all/modules/tims"] + path = sites/all/modules/tims + url = https://github.com/unlcms/tims.git +[submodule "vendor/Twig"] + path = vendor/Twig + url = https://github.com/fabpot/Twig.git diff --git a/README.md b/README.md index b15887f35cfc72b258296aa73ce75c5ad0c58e19..f8f45c3c338069b57e0d012486672c3030ef1286 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,6 @@ In this example the web root is /Library/WebServer/Documents and Apache runs as Added an example of the $default_domains array. Added the stub record needed for creating site aliases. - * modules/image/image.field.inc - - - theme_image_formatter ignores attributes so classes can't be added to an image in a theme (needed for photo frame). See http://drupal.org/node/1025796#comment-4298698 and http://drupal.org/files/issues/1025796.patch - * modules/field/modules/text/text.module - Add nl2br() on Plain Text processor. See http://drupal.org/node/1152216#comment-7174876 @@ -135,6 +131,10 @@ In this example the web root is /Library/WebServer/Documents and Apache runs as - Merge global redirect functions into Redirect module. See http://drupal.org/node/905914 + * upload_replace.module + + - Drupal 7 bug fixes. See http://drupal.org/node/1115484#comment-5646558 + * webform.module - Make Safe Key values accessible via tokens. See http://drupal.org/node/1340010#comment-6709520 Patch applied: http://drupal.org/files/webform-1340010-19.patch diff --git a/example.robots.txt b/example.robots.txt deleted file mode 100644 index 7de84356065bf3931329d6be21063f765d8bfaf0..0000000000000000000000000000000000000000 --- a/example.robots.txt +++ /dev/null @@ -1,60 +0,0 @@ -# -# robots.txt -# -# This file is to prevent the crawling and indexing of certain parts -# of your site by web crawlers and spiders run by sites like Yahoo! -# and Google. By telling these "robots" where not to go on your site, -# you save bandwidth and server resources. -# -# This file will be ignored unless it is at the root of your host: -# Used: http://example.com/robots.txt -# Ignored: http://example.com/site/robots.txt -# -# For more information about the robots.txt standard, see: -# http://www.robotstxt.org/wc/robots.html -# -# For syntax checking, see: -# http://www.sxw.org.uk/computing/robots/check.html - -User-agent: * -Crawl-delay: 10 -# Directories -Disallow: /includes/ -Disallow: /misc/ -Disallow: /modules/ -Disallow: /profiles/ -Disallow: /scripts/ -Disallow: /themes/ -# Files -Disallow: /CHANGELOG.txt -Disallow: /cron.php -Disallow: /INSTALL.mysql.txt -Disallow: /INSTALL.pgsql.txt -Disallow: /INSTALL.sqlite.txt -Disallow: /install.php -Disallow: /INSTALL.txt -Disallow: /LICENSE.txt -Disallow: /MAINTAINERS.txt -Disallow: /update.php -Disallow: /UPGRADE.txt -Disallow: /xmlrpc.php -# Paths (clean URLs) -Disallow: /admin/ -Disallow: /comment/reply/ -Disallow: /filter/tips/ -Disallow: /node/add/ -Disallow: /search/ -Disallow: /user/register/ -Disallow: /user/password/ -Disallow: /user/login/ -Disallow: /user/logout/ -# Paths (no clean URLs) -Disallow: /?q=admin/ -Disallow: /?q=comment/reply/ -Disallow: /?q=filter/tips/ -Disallow: /?q=node/add/ -Disallow: /?q=search/ -Disallow: /?q=user/password/ -Disallow: /?q=user/register/ -Disallow: /?q=user/login/ -Disallow: /?q=user/logout/ diff --git a/modules/image/image.field.inc b/modules/image/image.field.inc index e2a0249ed622bf20543ba6596836ad92db449a7b..60c0f5ac00ff2e0e3bf20dae85a54020ee68b8b0 100644 --- a/modules/image/image.field.inc +++ b/modules/image/image.field.inc @@ -620,12 +620,6 @@ function theme_image_formatter($variables) { $image['title'] = $item['title']; } - /* UNL change: patch http://drupal.org/files/issues/1025796.patch http://drupal.org/node/1025796#comment-4298698 */ - // Load the attributes into the image. - if (isset($item['attributes'])) { - $image['attributes'] = $item['attributes']; - } - if ($variables['image_style']) { $image['style_name'] = $variables['image_style']; $output = theme('image_style', $image); diff --git a/patches/upload_replace_error-1115484-13.patch b/patches/upload_replace_error-1115484-13.patch new file mode 100644 index 0000000000000000000000000000000000000000..32a0ac764ebb384f13e75f40f339deb61114bb68 --- /dev/null +++ b/patches/upload_replace_error-1115484-13.patch @@ -0,0 +1,109 @@ +diff --git a/upload_replace.module b/upload_replace.module +index 0489f2d..7d1d644 100644 +--- a/upload_replace.module ++++ b/upload_replace.module +@@ -15,60 +15,53 @@ + /** + * Implementation of hook_file_update() + */ +-function upload_replace_file_update(&$new_file) { ++function upload_replace_file_update($new_file) { + if (!$new_file->fid) { + //Nothing to do if no fileid + return; + } +- + $desired_destination = preg_replace('/_[0-9]+\.(.*)$/', '.$1', $new_file->uri); + $db_path = db_select('file_managed', 'f') +- ->fields('f', array('uri', )) ++ ->fields('f', array('uri')) + ->condition('fid', $new_file->fid) + ->execute() +- ->fetchField(); +- if ($db_path != $new_file->uri) { ++ ->fetchAssoc(); ++ if ($db_path['uri'] != $new_file->uri) { + //this happens when a reversion is being reverted +- $next_good_filepath = file_destination($desired_destination, FILE_EXISTS_RENAME); ++ $next_good_uri = file_destination($desired_destination, FILE_EXISTS_RENAME); + db_update('file_managed') +- ->fields(array('uri' => $next_good_filepath)) ++ ->fields(array('uri' => $next_good_uri)) + ->condition('fid', $new_file->fid) + ->execute(); + $new_file->uri = $desired_destination; + } + else { + //If the filename has be modified by adding a _0 value, or +- //on certain situations the filepath will not match the filepath in the db, such as ++ //on certain situations the uri will not match the uri in the db, such as + //when reverting a revision. When reverting a revision change the filename as well +- if (!strpos($new_file->uri, $new_file->uri)) { +- //the filename is not in the filepath, so drupal must have added a "_0" before the extension ++ if (!strpos($new_file->uri, $new_file->filename)) { ++ //the filename is not in the uri, so drupal must have added a "_0" before the extension + //find the file that is blocking this file from keeping the correct path + $result = db_select('file_managed', 'f') + ->fields('f') + ->condition('uri', $desired_destination) + ->execute(); + //@todo only one result is handled, should allow for multiple results +- $is_blocked = false; +- + foreach ($result as $file) { + $is_blocked = TRUE; + $blocking_file = $file; +- $tmp_destination = file_directory_temp() ."/$blocking_file->filename"; ++ $tmp_destination = file_directory_temp()."/test_-".$blocking_file->fid."_-".$blocking_file->filename.""; + } + +- $old_destination = $db_path; +- +- if ($old_destination == $desired_destination){ +- return; +- } ++ $old_destination = $db_path['uri']; + //Swap the files + if ($is_blocked) { +- //move the blocking file to a temporary location ++ //move the blocking file to a temparary location + if (!file_unmanaged_move($desired_destination, $tmp_destination)) { + drupal_set_message(t('The file %old could not be moved to %new', array('%old' => $desired_destination, '%new' => $tmp_destination)), 'error'); + return; + } +- //DRUPAL 7 no longer changes the source filepath during move ++ //DRUPAL 7 no longer changes the source uri during move + //move blocking file was successful, update the DB + db_update('file_managed') + ->fields(array('uri' => $tmp_destination)) +@@ -107,10 +100,10 @@ function upload_replace_file_update(&$new_file) { + //Have to clear the cache because the revision data is cached somewhere + /* + * Find the nids where this file is used +- $query = "SELECT DISTINCT nid FROM {files} WHERE fid=%d"; ++ $query = "SELECT DISTINCT id FROM {file_usage} WHERE fid=%d"; + $result = db_query($query, $new_file->fid); + while($data = db_fetch_object($result)) { +- cache_clear_all("content:$data->nid"); ++ cache_clear_all("node:$data->id"); + } + */ + //This is inefficent, but how can we determine what nodes use this file? +@@ -118,11 +111,12 @@ function upload_replace_file_update(&$new_file) { + } + + /** +- * HOOK_file_delete, update the filepath in the file object before deleting as we may have altered it above ++ * HOOK_file_delete, update the uri in the file object before deleting as we may have altered it above + * @param object $new_file + */ + /* +-function upload_replace_file_delete(&$file) { +- $file->filepath = db_result(db_query("SELECT filepath FROM {files} WHERE fid = %d", $file->fid)); ++function upload_replace_file_delete($file) { ++ $file->uri = db_result(db_query("SELECT uri FROM {file_managed} WHERE fid = %d", $file->fid)); + } +-*/ +\ No newline at end of file ++*/ ++ diff --git a/sites/all/modules/robotstxt/robots.txt b/robots.txt old mode 100755 new mode 100644 similarity index 87% rename from sites/all/modules/robotstxt/robots.txt rename to robots.txt index 91968f6bacb1da8bd1e7b887da6f28f1a4bde860..7b87acf25a1d3b790bf319f098ff59f6234e0189 --- a/sites/all/modules/robotstxt/robots.txt +++ b/robots.txt @@ -1,4 +1,3 @@ -# $Id: robots.txt,v 1.8.2.2 2011/01/05 23:24:10 hass Exp $ # # robots.txt # @@ -31,6 +30,7 @@ Disallow: /CHANGELOG.txt Disallow: /cron.php Disallow: /INSTALL.mysql.txt Disallow: /INSTALL.pgsql.txt +Disallow: /INSTALL.sqlite.txt Disallow: /install.php Disallow: /INSTALL.txt Disallow: /LICENSE.txt @@ -41,6 +41,7 @@ Disallow: /xmlrpc.php # Paths (clean URLs) Disallow: /admin/ Disallow: /comment/reply/ +Disallow: /filter/tips/ Disallow: /node/add/ Disallow: /search/ Disallow: /user/register/ @@ -50,9 +51,15 @@ Disallow: /user/logout/ # Paths (no clean URLs) Disallow: /?q=admin/ Disallow: /?q=comment/reply/ +Disallow: /?q=filter/tips/ Disallow: /?q=node/add/ Disallow: /?q=search/ Disallow: /?q=user/password/ Disallow: /?q=user/register/ Disallow: /?q=user/login/ Disallow: /?q=user/logout/ +# UNL +Disallow: /book/export/html/ +Disallow: /*/book/export/html/ +Disallow: /?q=book/export/html/ +Disallow: /?q=*/book/export/html/ diff --git a/sites/all/libraries/Twig b/sites/all/libraries/Twig new file mode 120000 index 0000000000000000000000000000000000000000..736c45ab6e6a576df1e5288b765caf01e0fbbf02 --- /dev/null +++ b/sites/all/libraries/Twig @@ -0,0 +1 @@ +../../../vendor/Twig \ No newline at end of file diff --git a/sites/all/modules/breakpoints/LICENSE.txt b/sites/all/modules/breakpoints/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/breakpoints/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/breakpoints/README.txt b/sites/all/modules/breakpoints/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..63e0614635fb406a9be5a47d498ce920c3f3320a --- /dev/null +++ b/sites/all/modules/breakpoints/README.txt @@ -0,0 +1,8 @@ +-- SUMMARY -- + +Breakpoints + +Multipliers +----------- + +For the moment hard-coded: variable_get('breakpoints_multipliers', array('1x', '1.5x', '2x')); \ No newline at end of file diff --git a/sites/all/modules/breakpoints/breakpoints.admin.inc b/sites/all/modules/breakpoints/breakpoints.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..e780c4109205b9e2a5e62ab27508018873d95fe6 --- /dev/null +++ b/sites/all/modules/breakpoints/breakpoints.admin.inc @@ -0,0 +1,1216 @@ +<?php + +/** + * @file + * Breakpoints - admin settings + */ + +/** + * Admin form + */ +function breakpoints_admin_breakpoints($form, &$form_state, $breakpoint_group_name = '') { + $form = array(); + + // In case of an export to theme, there will be a variable exported_breakpoints + if (isset($form_state['exported_breakpoints']) && !empty($form_state['exported_breakpoints'])) { + $form['exported_breakpoints'] = array( + '#title' => t('Copy/Paste the following inside your theme.info file.'), + '#type' => 'textarea', + '#default_value' => $form_state['exported_breakpoints'], + ); + } + // Global is the same as no group name + $global = FALSE; + if ($breakpoint_group_name == '' || $breakpoint_group_name == 'global') { + $breakpoint_group_name = ''; + $global = TRUE; + } + + $form_state['group_name'] = $breakpoint_group_name; + + $settings = breakpoints_settings(); + $multipliers = array(); + if (isset($settings->multipliers) && !empty($settings->multipliers)) { + $multipliers = drupal_map_assoc(array_values($settings->multipliers)); + if (array_key_exists('1x', $multipliers)) { + unset($multipliers['1x']); + } + } + + if ($global) { + $form['info'] = array( + '#type' => 'markup', + '#markup' => t("You can manage all your breakpoints on this screen, if one of your themes has breakpoints defined inside the .info file they will be shown here."), + ); + + $info = array(); + $info[] = t("To create a new breakpoint, you have to enter a name and a media query (ex. (min-width: 15em))."); + $info[] = t("All breakpoints can be enabled or disabled so they cannot be used by other modules."); + $info[] = t("For each breakpoint you can define what multipliers have to be available (needed to support 'retina' displays)."); + $info[] = t("Breakpoints you created yourself can be deleted."); + $info[] = t("You can group multiple breakpoints in a group by using '!add', so other modules can easily interact with them.", array('!add' => l(t('Add a new group'), 'admin/config/media/breakpoints/groups/add'))); + + $form['more_info'] = array( + '#type' => 'container', + '#theme' => 'item_list', + '#items' => $info, + ); + } + else { + $form['info'] = array( + '#type' => 'markup', + '#markup' => t("You can manage the breakpoints of this group here."), + ); + $info = array(); + $info[] = t("You can change the order of the breakpoints inside this group."); + $info[] = t("You can enable multipliers for each breakpoint, but this will also affect other groups."); + + $form['more_info'] = array( + '#type' => 'container', + '#theme' => 'item_list', + '#items' => $info, + ); + } + + $form['#attached']['css'][] = drupal_get_path('module', 'breakpoints') . '/css/breakpoints.admin.css'; + $form['breakpoints'] = array( + '#type' => 'container', + '#tree' => TRUE, + '#theme' => 'breakpoints_admin_breakpoints_table', + '#multipliers' => $multipliers, + '#group_name' => $breakpoint_group_name, + ); + + $breakpoints = array(); + $breakpoint_group = breakpoints_breakpoint_group_load($breakpoint_group_name); + if ($global) { + $breakpoints = breakpoints_breakpoint_load_all(); + } + else { + $weight = 0; + foreach ($breakpoint_group->breakpoints as $breakpoint_name) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint_name); + if ($breakpoint && isset($breakpoint->machine_name)) { + $breakpoint->global_weight = $breakpoint->weight; + $breakpoint->weight = $weight++; + $breakpoints[$breakpoint_name] = $breakpoint; + } + } + } + + foreach ($breakpoints as $key => $breakpoint) { + if ($breakpoint->source_type == BREAKPOINTS_SOURCE_TYPE_THEME) { + $bp_group = breakpoints_breakpoint_group_load($breakpoint->source); + if ($bp_group->overridden && variable_get('breakpoints_hide_overridden_breakpoints', 1) && $global) { + continue; + } + } + $form['breakpoints'][$key] = array( + '#breakpoint_data' => $breakpoint, + 'name' => array( + '#type' => 'textfield', + '#default_value' => $breakpoint->name, + '#disabled' => TRUE, + '#size' => 20, + ), + 'breakpoint' => array( + '#type' => 'textfield', + '#default_value' => $breakpoint->breakpoint, + '#disabled' => $breakpoint->source_type === 'theme' || !$global, + '#size' => empty($multipliers) ? 60 : 30, + '#maxlength' => 255, + ), + 'weight' => array( + '#type' => 'textfield', + '#size' => 4, + '#default_value' => isset($breakpoint->weight) ? $breakpoint->weight : 0, + '#attributes' => array('class' => array('breakpoints-weight')), + ), + ); + // Add multipliers checkboxes if needed. + if (!empty($multipliers)) { + $form['breakpoints'][$key]['multipliers'] = array( + '#type' => 'checkboxes', + '#default_value' => (isset($breakpoint->multipliers) && is_array($breakpoint->multipliers)) ? $breakpoint->multipliers : array(), + '#options' => $multipliers, + ); + } + // Add global weight if needed. + $form['breakpoints'][$key]['global_weight'] = array( + '#type' => 'value', + '#value' => isset($breakpoint->global_weight) ? $breakpoint->global_weight : $breakpoint->weight, + ); + } + + if ($global) { + // Add empty row + $form['breakpoints']['new'] = array( + 'name' => array( + '#type' => 'textfield', + '#default_value' => '', + '#size' => 20, + '#maxlength' => 255, + ), + 'machine_name' => array( + '#type' => 'machine_name', + '#size' => '64', + '#title' => t('Machine name'), + '#default_value' => '', + '#machine_name' => array( + 'exists' => 'breakpoints_breakpoint_name_exists', + 'source' => array('breakpoints', 'new', 'name'), + ), + '#required' => FALSE, + '#maxlength' => 255, + ), + 'breakpoint' => array( + '#type' => 'textfield', + '#default_value' => '', + '#size' => empty($multipliers) ? 60 : 30, + '#maxlength' => 255, + ), + 'weight' => array( + '#type' => 'textfield', + '#size' => 4, + '#default_value' => 0, + '#attributes' => array('class' => array('breakpoints-weight')), + ), + ); + // Add multipliers checkboxes if needed. + if (!empty($multipliers)) { + $form['breakpoints']['new']['multipliers'] = array( + '#type' => 'checkboxes', + '#default_value' => array(), + '#options' => $multipliers, + ); + } + } + + // Buttons + $form['buttons'] = array( + '#type' => 'container', + ); + + // Submit button + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + if (!$global) { + switch ($breakpoint_group->type) { + case BREAKPOINTS_SOURCE_TYPE_THEME: + if (!$breakpoint_group->overridden) { + $form['buttons']['override'] = array( + '#type' => 'submit', + '#value' => t('Override theme breakpoints'), + '#submit' => array('breakpoints_admin_breakpoints_submit_override'), + ); + $form['buttons']['reload'] = array( + '#type' => 'submit', + '#value' => t('Reload theme breakpoints'), + '#submit' => array('breakpoints_admin_breakpoints_submit_reload'), + ); + $form['buttons']['duplicate'] = array( + '#type' => 'markup', + '#markup' => l( + t('Duplicate group'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/duplicate', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-duplicate-link')), + ) + ), + ); + } + else { + $form['buttons']['exporttotheme'] = array( + '#type' => 'submit', + '#value' => t('Export breakpoints to theme'), + '#submit' => array('breakpoints_admin_breakpoints_submit_exporttotheme'), + ); + $form['buttons']['revert'] = array( + '#type' => 'submit', + '#value' => t('Revert theme breakpoints'), + '#submit' => array('breakpoints_admin_breakpoints_submit_revert'), + ); + $form['buttons']['editlink'] = array( + '#type' => 'markup', + '#markup' => l( + t('Edit group breakpoints'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/edit', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-edit-link')), + ) + ), + ); + $form['buttons']['duplicate'] = array( + '#type' => 'markup', + '#markup' => l( + t('Duplicate group'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/duplicate', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-duplicate-link')), + ) + ), + ); + } + break; + case BREAKPOINTS_SOURCE_TYPE_MODULE: + $form['buttons']['exporttotheme'] = array( + '#type' => 'submit', + '#value' => t('Export breakpoints to theme'), + '#submit' => array('breakpoints_admin_breakpoints_submit_exporttotheme'), + ); + $form['buttons']['editlink'] = array( + '#type' => 'markup', + '#markup' => l( + t('Edit group breakpoints'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/edit', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-edit-link')), + ) + ), + ); + $form['buttons']['duplicate'] = array( + '#type' => 'markup', + '#markup' => l( + t('Duplicate group'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/duplicate', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-duplicate-link')), + ) + ), + ); + break; + case BREAKPOINTS_SOURCE_TYPE_CUSTOM: + $form['buttons']['exporttotheme'] = array( + '#type' => 'submit', + '#value' => t('Export breakpoints to theme'), + '#submit' => array('breakpoints_admin_breakpoints_submit_exporttotheme'), + ); + $form['buttons']['editlink'] = array( + '#type' => 'markup', + '#markup' => l( + t('Edit group breakpoints'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/edit', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-edit-link')), + ) + ), + ); + $form['buttons']['duplicate'] = array( + '#type' => 'markup', + '#markup' => l( + t('Duplicate group'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/duplicate', + array( + 'query' => drupal_get_destination(), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-duplicate-link')), + ) + ), + ); + $form['buttons']['deletelink'] = array( + '#type' => 'markup', + '#markup' => l( + t('Delete this group'), 'admin/config/media/breakpoints/groups/' . $breakpoint_group_name . '/delete', + array( + 'query' => array('destination' => 'admin/config/media/breakpoints/groups'), + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-delete-link')), + ) + ), + ); + break; + } + } + + return $form; +} + +/** + * Theme form as table. + */ +function theme_breakpoints_admin_breakpoints_table($variables) { + drupal_add_css(drupal_get_path('module', 'breakpoints') . '/css/breakpoints.admin.css'); + $form = $variables['form']; + $global = empty($form['#group_name']); + // Rows. + $rows = array(); + foreach (element_children($form) as $key) { + + if ($key != 'new') { + $row = _breakpoints_admin_breakpoints_table_row($form[$key], $key, $global); + $breakpoint = $form[$key]['#breakpoint_data']; + $class = 'breakpoints-status-' . ($breakpoint->status ? 'enabled' : 'disabled'); + } + else { + $row = _breakpoints_admin_breakpoints_table_new_row($form[$key]); + $class = 'breakpoints-status-new'; + } + + $rows[] = array( + 'data' => $row, + 'class' => array('draggable', $class), + ); + } + + // Header. + $header = array(); + $header[] = array('data' => t('Name'), 'colspan' => 2); + $header[] = t('Breakpoint, @media ...'); + $header[] = t('Multipliers'); + $header[] = t('Source'); + $header[] = t('Status'); + if ($global) { + $header[] = array('data' => t('Operations'), 'colspan' => 3); + } + $header[] = t('Weight'); + + $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'resp-img-breakpoints'))); + drupal_add_tabledrag('resp-img-breakpoints', 'order', 'sibling', 'breakpoints-weight'); + return $output; +} + +/** + * Helper callback for theme_breakpoints_admin_breakpoints_table(). + */ +function _breakpoints_admin_breakpoints_table_row(&$element, $key, $global) { + $row = array(); + $link_attributes = array( + 'attributes' => array( + 'class' => array('image-style-link'), + ), + ); + $breakpoint = $element['#breakpoint_data']; + $element['weight']['#attributes']['class'] = array('breakpoints-weight'); + + $row[] = drupal_render($element['name']); + $row[] = ''; + $row[] = drupal_render($element['breakpoint']); + $row[] = drupal_render($element['multipliers']); + $row[] = $breakpoint->source . ' (' . $breakpoint->source_type . ')'; + $row[] = $breakpoint->status ? t('Enabled') : t('Disabled'); + + if ($global) { + $row[] = l($breakpoint->status ? t('Disable') : t('Enable'), 'admin/config/media/breakpoints/' . ($breakpoint->status ? 'disable' : 'enable') . '/' . $key, $link_attributes); + $row[] = $breakpoint->source_type == BREAKPOINTS_SOURCE_TYPE_CUSTOM ? l(t('Delete'), 'admin/config/media/breakpoints/delete/' . $key, $link_attributes) : ''; + $row[] = l(t('Export'), 'admin/config/media/breakpoints/export/' . $key, $link_attributes); + } + + $row[] = drupal_render($element['weight']); + return $row; +} + +/** + * Helper callback for theme_breakpoints_admin_breakpoints_table(). + */ +function _breakpoints_admin_breakpoints_table_new_row(&$element) { + $row = array(); + $row[] = drupal_render($element['name']); + $row[] = drupal_render($element['machine_name']); + $row[] = drupal_render($element['breakpoint']); + $row[] = drupal_render($element['multipliers']); + $row[] = ''; + $row[] = ''; + $row[] = ''; + $row[] = ''; + $row[] = ''; + $row[] = drupal_render($element['weight']); + return $row; +} + +/** + * Admin form validation. + */ +function breakpoints_admin_breakpoints_validate($form, &$form_state) { + if (strpos($form_state['triggering_element']['#id'], 'remove') === FALSE) { + $breakpoints = $form_state['values']['breakpoints']; + if (!empty($breakpoints)) { + foreach ($breakpoints as $key => $breakpointdata) { + if (!empty($breakpointdata['name'])) { + // Breakpoint is required. + if ($key == 'new') { + if (empty($breakpointdata['machine_name'])) { + form_set_error('breakpoints][' . $key . '][machine_name', 'Machine name field is required'); + } + } + if (empty($breakpointdata['breakpoint']) && $breakpointdata['breakpoint'] !== '0') { + form_set_error('breakpoints][' . $key . '][breakpoint', 'Breakpoint field is required'); + } + } + } + } + } +} + +/** + * Admin form submit. + */ +function breakpoints_admin_breakpoints_submit($form, &$form_state) { + $breakpoints = $form_state['values']['breakpoints']; + $group_name = $form_state['group_name']; + $global_group = $group_name == ''; + $group = breakpoints_breakpoint_group_empty_object(); + if (!$global_group) { + // sort by weight, needed to store the right order in a group + uasort($breakpoints, '_breakpoints_sort_by_weight_array'); + } + $saved_breakpoints = array(); + if (!empty($breakpoints)) { + foreach ($breakpoints as $breakpointname => $breakpointdata) { + if (!empty($breakpointdata['name'])) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpointname); + if ($breakpoint && $breakpointname != 'new') { + // only save the weight when on the global screen. + if ($global_group) { + $breakpoint->weight = $breakpointdata['weight']; + } + else { + $breakpoint->weight = $breakpointdata['global_weight']; + } + $breakpoint->breakpoint = $breakpointdata['breakpoint']; + $breakpoint->multipliers = $breakpointdata['multipliers']; + breakpoints_breakpoint_save($breakpoint); + $saved_breakpoints[] = $breakpointname; + } + else { + $breakpoint = new stdClass(); + $breakpoint->name = $breakpointdata['name']; + $breakpoint->breakpoint = $breakpointdata['breakpoint']; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->weight = $breakpointdata['weight']; + $breakpoint->status = TRUE; + $breakpoint->multipliers = $breakpointdata['multipliers']; + $breakpoint->machine_name = 'custom.user.' . $breakpointdata['machine_name']; + breakpoints_breakpoint_save($breakpoint); + $saved_breakpoints[] = breakpoints_breakpoint_config_name($breakpoint); + } + } + } + if (!$global_group) { + $group = breakpoints_breakpoint_group_load($group_name); + if ($group) { + $group->breakpoints = $saved_breakpoints; + breakpoints_breakpoint_group_save($group); + } + } + } +} + +/** + * Admin form submit - Override theme breakpoints. + */ +function breakpoints_admin_breakpoints_submit_override($form, &$form_state) { + $group_name = $form_state['group_name']; + $group = breakpoints_breakpoint_group_empty_object(); + $global_group = $group_name == ''; + if (!$global_group) { + $group = breakpoints_breakpoint_group_load($group_name); + if ($group) { + breakpoints_breakpoints_group_override($group); + } + } +} + +/** + * Admin form submit - Revert theme breakpoints. + */ +function breakpoints_admin_breakpoints_submit_revert($form, &$form_state) { + $group_name = $form_state['group_name']; + $global_group = $group_name == ''; + if (!$global_group) { + $group = breakpoints_breakpoint_group_load($group_name); + if ($group) { + breakpoints_breakpoints_group_revert($group); + } + } +} + +/** + * Admin form submit - Reload theme breakpoints. + */ +function breakpoints_admin_breakpoints_submit_reload($form, &$form_state) { + $group_name = $form_state['group_name']; + $global_group = $group_name == ''; + if (!$global_group) { + $group = breakpoints_breakpoint_group_load($group_name); + if ($group) { + breakpoints_breakpoints_group_reload($group); + } + } +} + +/** + * Admin form submit - Export breakpoints to theme. + */ +function breakpoints_admin_breakpoints_submit_exporttotheme($form, &$form_state) { + $group_name = $form_state['group_name']; + $global_group = $group_name == ''; + if (!$global_group) { + $group = breakpoints_breakpoint_group_load($group_name); + if ($group) { + $breakpoints = breakpoints_breakpoints_group_exporttotheme($group); + if ($breakpoints) { + $export = array(); + foreach ($breakpoints as $breakpoint_name => $breakpoint) { + $export[] = 'breakpoints[' . $breakpoint_name . '] = ' . $breakpoint; + } + $form_state['exported_breakpoints'] = implode("\n", $export); + $form_state['rebuild'] = TRUE; + } + } + } +} + +/** + * Page callback. + */ +function breakpoints_admin_breakpoint_actions_page($group_name, $action, $breakpoint) { + if (in_array($action, array('enable', 'disable', 'delete', 'export'))) { + return drupal_get_form('breakpoints_admin_breakpoint_actions_form', $group_name, $action, $breakpoint); + } + return MENU_NOT_FOUND; +} + +/** + * Admin action form: enable, disable, delete, export + */ +function breakpoints_admin_breakpoint_actions_form($form, &$form_state, $group_name, $action, $breakpoint) { + switch ($action) { + case 'enable': + case 'disable': + case 'delete': + $form_state['group_name'] = $group_name; + $form_state['action'] = $action; + $form_state['breakpoint'] = $breakpoint; + $question = t('Are you sure you want to %action %breakpoint', array( + '%action' => $action, + '%breakpoint' => $breakpoint, + )); + if (!empty($group_name)) { + $path = 'admin/config/media/breakpoints/groups/' . $group_name; + } + else { + $path = 'admin/config/media/breakpoints'; + } + $form = confirm_form($form, $question, $path, ''); + break; + case 'export': + $form = drupal_get_form('breakpoints_admin_breakpoint_export_form', $breakpoint); + break; + } + return $form; +} + +/** + * Admin action form submit + */ +function breakpoints_admin_breakpoint_actions_form_submit($form, &$form_state) { + $group_name = $form_state['group_name']; + $action = $form_state['action']; + $breakpoint = $form_state['breakpoint']; + switch ($action) { + case 'delete': + breakpoints_breakpoint_delete_by_fullkey($breakpoint); + break; + case 'enable': + case 'disable': + breakpoints_breakpoint_toggle_status($breakpoint); + break; + } + if (!empty($group_name)) { + $form_state['redirect'] = 'admin/config/media/breakpoints/groups/' . $group_name; + } + else { + $form_state['redirect'] = 'admin/config/media/breakpoints'; + } +} + +function breakpoints_add_style_form($form, &$form_state) { + module_load_include('inc', 'image', 'image.admin'); + $form['style'] = array( + '#title' => t('Image style'), + '#type' => 'select', + '#options' => array_filter(image_style_options(FALSE), '_breakpoints_filter_styles'), + '#required' => TRUE, + '#description' => t('This image style will be cloned to create the responsive style'), + ); + + $form['base_name'] = array( + '#type' => 'textfield', + '#size' => '64', + '#title' => t('Image style base name'), + '#description' => t('The name is used in URLs for generated images. Use only lowercase alphanumeric characters, underscores (_), and hyphens (-).'), + '#element_validate' => array('image_style_name_validate'), + '#required' => TRUE, + ); + $breakpoints = breakpoints_breakpoint_load_all_active(); + if (isset($breakpoints) && !empty($breakpoints)) { + $options = array(); + foreach ($breakpoints as $breakpoint) { + foreach ($breakpoint->multipliers as $multiplier) { + $options[str_replace('.', '_', $breakpoint->machine_name . '_' . $multiplier)] = $breakpoint->name . ' [' . $breakpoint->breakpoint . ', multiplier:' . $multiplier . ']'; + } + } + $form['breakpoints'] = array( + '#title' => t('breakpoints'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => drupal_map_assoc(array_keys($options)), + '#description' => t('Select the breakpoints to create an image style for'), + '#required' => TRUE, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Create'), + ); + } + else { + $form['redirect_link'] = array( + '#markup' => t("You need to create a breakpoint first before creating a responsive style. ") . l('Click here', 'admin/config/media/breakpoints') . t(" to continue with configuring breakpoints."), + ); + } + return $form; +} + +function breakpoints_add_style_form_validate($form, &$form_state) { + foreach (array_filter($form_state['values']['breakpoints']) as $breakpoint) { + if (image_style_load($form_state['values']['base_name'] . $breakpoint)) { + form_set_error('breakpoints', t('An image style with the name !name already exists', array('!name' => $form_state['values']['base_name'] . $breakpoint))); + } + } +} + +function breakpoints_add_style_form_submit($form, &$form_state) { + $base = image_style_load($form_state['values']['style']); + if (!isset($base['effects'])) { + $base['effects'] = array(); + } + foreach (array_filter($form_state['values']['breakpoints']) as $breakpoint) { + $new_style = array( + 'name' => $form_state['values']['base_name'] . $breakpoint, + ); + $style = image_style_save($new_style); + if ($style) { + foreach ($base['effects'] as $effect) { + $effect['isid'] = $style['isid']; + unset($effect['ieid']); + image_effect_save($effect); + } + } + } + $form_state['redirect'] = 'admin/config/media/image-styles'; + drupal_set_message(t('The new styles have been created')); +} + +function breakpoints_admin_breakpoint_group_edit_form($form, &$form_state, $machine_name = '') { + $form = array(); + $group = breakpoints_breakpoint_group_load($machine_name); + $breakpoints = breakpoints_breakpoint_load_all(); + if (empty($breakpoints)) { + return breakpoints_admin_breakpoint_group_edit_form_no_breakpoints(); + } + + $form_state['#breakpoint_group'] = $group; + $is_new = $machine_name == ''; + $form_state['#is_new'] = $is_new; + + $form['name'] = array( + '#type' => 'textfield', + '#size' => '64', + '#title' => t('group name'), + '#required' => TRUE, + '#default_value' => isset($group->name) ? $group->name : '', + '#disabled' => !$is_new, + ); + + $form['machine_name'] = array( + '#type' => 'machine_name', + '#size' => '64', + '#title' => t('Machine name'), + '#required' => TRUE, + '#default_value' => isset($group->machine_name) ? $group->machine_name : '', + '#disabled' => !$is_new, + '#machine_name' => array( + 'exists' => 'breakpoints_breakpoint_group_name_exists', + ), + ); + + foreach ($breakpoints as $breakpoint_name => $breakpoint) { + $options[$breakpoint_name] = $breakpoint->name . ' [' . $breakpoint->breakpoint . ']'; + } + + $form['breakpoints'] = array( + '#title' => 'Select the breakpoints you want to use in this group', + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => isset($group->breakpoints) ? drupal_map_assoc($group->breakpoints) : array(), + '#required' => TRUE, + ); + + // Buttons + $form['buttons'] = array( + '#type' => 'container', + ); + + // Submit button + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + if (!$is_new && $group->type == BREAKPOINTS_SOURCE_TYPE_CUSTOM) { + $form['buttons']['deletelink'] = array( + '#type' => 'markup', + '#markup' => l(t('Delete this group'), 'admin/config/media/breakpoints/groups/' . $group->machine_name . '/delete', array( + 'query' => drupal_get_destination(), + )), + ); + } + + return $form; +} + +function breakpoints_admin_breakpoint_group_edit_form_no_breakpoints() { + $form = array(); + + $form['info'] = array( + '#type' => 'markup', + '#markup' => t("There're no breakpoints defined, you'll have to create them first."), + ); + + return $form; +} + +function breakpoints_admin_breakpoint_group_edit_form_validate($form, &$form_state) { + $name = $form_state['values']['machine_name']; + $label = $form_state['values']['name']; +} + +function breakpoints_admin_breakpoint_group_edit_form_submit($form, &$form_state) { + $machine_name = $form_state['values']['machine_name']; + $name = $form_state['values']['name']; + $breakpoints = array(); + foreach ($form_state['values']['breakpoints'] as $breakpoint => $status) { + if ($status) { + $breakpoints[] = $breakpoint; + } + } + $is_new = $form_state['#is_new']; + + if ($is_new) { + $new_group = breakpoints_breakpoint_group_empty_object(); + $new_group->machine_name = $machine_name; + $new_group->name = $name; + $new_group->type = BREAKPOINTS_SOURCE_TYPE_CUSTOM; + $new_group->breakpoints = $breakpoints; + breakpoints_breakpoint_group_save($new_group); + menu_rebuild(); + $form_state['redirect'] = 'admin/config/media/breakpoints/groups/' . $machine_name; + drupal_set_message(t('The new group have been created')); + } + else { + $existing_group = breakpoints_breakpoint_group_load($machine_name); + $existing_group->breakpoints = $breakpoints; + breakpoints_breakpoint_group_save($existing_group); + } +} + +/** + * Delete a group. + */ +function breakpoints_admin_breakpoint_group_delete_form($form, &$form_state, $machine_name) { + $group = breakpoints_breakpoint_group_load($machine_name); + $form_state['machine_name'] = $machine_name; + $question = t('Are you sure you want to delete %group', array( + '%group' => $group->name, + )); + $path = 'admin/config/media/breakpoints/groups'; + return confirm_form($form, $question, $path, ''); +} + +/** + * Delete a group. + */ +function breakpoints_admin_breakpoint_group_delete_form_submit($form, &$form_state) { + $machine_name = $form_state['machine_name']; + breakpoints_breakpoint_group_delete_by_name($machine_name); + menu_rebuild(); + $form_state['redirect'] = 'admin/config/media/breakpoints'; +} + +/** + * Export a group. + */ +function breakpoints_admin_breakpoint_group_export_form($form, &$form_state, $machine_name) { + // Create the export code textarea. + ctools_include('export'); + $group = breakpoints_breakpoint_group_load($machine_name); + if (!$group || !$machine_name) { + $group = new stdClass(); + } + $group_export = breakpoints_breakpoint_group_export($group); + + $form['group_export'] = array( + '#type' => 'textarea', + '#title' => t('Breakpoint group code'), + '#rows' => count(explode("\n", $group_export)), + '#default_value' => $group_export, + ); + + $breakpoints_export = NULL; + if (isset($group->breakpoints)) { + foreach ($group->breakpoints as $breakpoint) { + if (!is_array($breakpoint) && !is_object($breakpoint)) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint); + } + $breakpoints_export .= ctools_export_object('breakpoints', $breakpoint); + $breakpoints_export .= "\n"; + } + } + + $form['breakpoints_export'] = array( + '#type' => 'textarea', + '#title' => t('Breakpoints code'), + '#rows' => count(explode("\n", $breakpoints_export)), + '#default_value' => $breakpoints_export, + ); + + return $form; +} + +/** + * Import a breakpoint group. + */ +function breakpoints_admin_breakpoint_group_import_form($form, &$form_state) { + $form['import'] = array( + '#type' => 'textarea', + '#rows' => 10, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import') + ); + return $form; +} + +/** + * Validate a breakpoint group import. + */ +function breakpoints_admin_breakpoint_group_import_form_validate($form, &$form_state) { + ctools_include('export'); + $code = $form_state['values']['import']; + $group = ctools_export_crud_import('breakpoint_group', $code); + if (!breakpoints_breakpoint_group_validate($group)) { + form_set_error('import', t('Not a valid group object')); + return; + } + if (breakpoints_breakpoint_group_name_exists($group->machine_name)) { + form_set_error('import', t('A group with machine name %name already exists', array('%name' => $group->machine_name))); + return; + } + foreach ($group->breakpoints as $key => $breakpoint) { + // check if the breakpoint is a fully loaded object. + if (is_array($breakpoint) || is_object($breakpoint)) { + if (!breakpoints_breakpoint_validate($breakpoint)) { + form_set_error('import', t('The breakpoint group contains an invalid breakpoint.')); + return; + } + } + } + // Manually imported groups are the same as custom made groups. + $group->type = BREAKPOINTS_SOURCE_TYPE_CUSTOM; + form_set_value($form['import'], $group, $form_state); +} + +/** + * Import breakpoint group. + */ +function breakpoints_admin_breakpoint_group_import_form_submit($form, &$form_state) { + $group = $form_state['values']['import']; + + foreach ($group->breakpoints as $key => $breakpoint) { + // check if the breakpoint is a fully loaded object. + if (is_array($breakpoint) || is_object($breakpoint)) { + $breakpoint = (object)$breakpoint; + // If the breakpoints exist, only overwrite the custom ones. + if ($existing_breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint->machine_name)) { + if ($breakpoint->source_type == BREAKPOINTS_SOURCE_TYPE_CUSTOM) { + $breakpoint = (object)array_merge((array)$existing_breakpoint, (array)$breakpoint); + breakpoints_breakpoint_save($breakpoint); + } + } + else { + breakpoints_breakpoint_save($breakpoint); + } + $group->breakpoints[$key] = $breakpoint->machine_name; + } + } + if (breakpoints_breakpoint_group_save($group)) { + drupal_set_message(t('Group %group saved.', array('%group' => $group->name))); + $form_state['redirect'] = 'admin/config/media/breakpoints/groups/' . $group->machine_name; + } + else { + drupal_set_message(t('Something went wrong, we could not save the group', 'error')); + } +} + +/** + * Export a breakpoint. + */ +function breakpoints_admin_breakpoint_export_form($form, $form_state, $fullkey) { + // Create the export code textarea. + ctools_include('export'); + $breakpoint = breakpoints_breakpoint_load_by_fullkey($fullkey); + if (!$breakpoint) { + $breakpoint = new stdClass(); + } + $export = ctools_export_object('breakpoints', $breakpoint); + + $form['export'] = array( + '#type' => 'textarea', + '#title' => t('Breakpoint code'), + '#rows' => 20, + '#default_value' => $export, + ); + return $form; +} + +/** + * Import breakpoint. + */ +function breakpoints_admin_breakpoint_import_form($form, $form_state) { + $form['import'] = array( + '#type' => 'textarea', + '#rows' => 10, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import') + ); + return $form; +} + +/** + * Validate a breakpoint import. + */ +function breakpoints_admin_breakpoint_import_form_validate($form, &$form_state) { + ctools_include('export'); + $code = $form_state['values']['import']; + $breakpoint = ctools_export_crud_import('breakpoints', $code); + if (!breakpoints_breakpoint_validate($breakpoint)) { + form_set_error('import', t('Not a valid breakpoint object')); + } + else { + if (breakpoints_breakpoint_machine_name_exists($breakpoint->machine_name)) { + form_set_error('import', t('A breakpoint with machine name %name already exists', array('%name' => $breakpoint->machine_name))); + } + else { + form_set_value($form['import'], $breakpoint, $form_state); + } + } +} + +/** + * Import breakpoint. + */ +function breakpoints_admin_breakpoint_import_form_submit($form, &$form_state) { + $breakpoint = $form_state['values']['import']; + if (breakpoints_breakpoint_save($breakpoint)) { + drupal_set_message(t('Breakpoint %breakpoint saved.', array('%breakpoint' => $breakpoint->name))); + $form_state['redirect'] = 'admin/config/media/breakpoints/'; + } + else { + drupal_set_message(t('Something went wrong, we could not save the breakpoint'), 'error'); + } +} + +/** + * Multipliers administration form. + */ +function breakpoints_multipliers_form($form, &$form_state) { + $settings = breakpoints_settings(); + $multipliers = drupal_map_assoc($settings->multipliers); + if (isset($multipliers['1x'])) { + unset($multipliers['1x']); + } + $form['multipliers'] = array( + '#type' => 'container', + '#tree' => TRUE, + '#theme' => 'breakpoints_multipliers_table_form', + ); + $form['multipliers']['1x'] = array( + '#markup' => '1x', + ); + foreach ($multipliers as $multiplier) { + $form['multipliers'][$multiplier] = array( + '#type' => 'textfield', + '#title' => '', + '#required' => FALSE, + '#default_value' => $multiplier, + ); + } + $form['multipliers']['new'] = array( + '#type' => 'textfield', + '#title' => '', + '#required' => FALSE, + '#default_value' => '', + '#description' => t('Multiplier like 1.5x, 2x.'), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + return $form; +} + +function theme_breakpoints_multipliers_table_form($element) { + $form = $element['form']; + $header = array(t('Label'), t('Operations')); + foreach (element_children($form) as $multiplier) { + $row = array(); + $row[] = drupal_render($form[$multiplier]); + $row[] = in_array($multiplier, array('new', '1x')) ? '' : l(t('Delete'), 'admin/config/media/breakpoints/multipliers/' . $multiplier . '/delete'); + $rows[] = $row; + } + return theme('table', array('header' => $header, 'rows' => $rows)); +} + +function breakpoints_multipliers_form_validate($form, $form_state) { + $multipliers = $form_state['values']['multipliers']; + $saved_multipliers = array( + '1x' => '1x', + ); + foreach ($multipliers as $form_key => $multiplier) { + if ($multiplier == '' && $form_key != 'new') { + form_set_error('multipliers][' . $form_key, t('Label is required.')); + } + if ($multiplier != '') { + if (isset($saved_multipliers[$multiplier])) { + form_set_error('multipliers][' . $form_key, t('Label must be unique.')); + } + $saved_multipliers[$multiplier] = $multiplier; + } + } +} + +function breakpoints_multipliers_form_submit($form, &$form_state) { + $multipliers = array_values(array_filter($form_state['values']['multipliers'])); + array_unshift($multipliers, '1x'); + breakpoints_settings_save($multipliers); + drupal_set_message(t('Multiplier settings are saved.')); +} + +function breakpoints_admin_multiplier_delete_form($form, &$form_state, $multiplier) { + $path = 'admin/config/media/breakpoints/multipliers'; + if ($multiplier == '1x') { + $form['multiplier'] = array( + '#markup' => t('Multiplier %multiplier can not be deleted! !link', array('%multiplier' => $multiplier, '!link' => l(t('Back to overview page.'), $path))) + ); + return $form; + } + $form['multiplier'] = array( + '#type' => 'value', + '#value' => $multiplier, + ); + return confirm_form($form, t('Are you sure you want to delete multiplier %multiplier', array('%multiplier' => $multiplier)), $path); +} + +function breakpoints_admin_multiplier_delete_form_submit($form, &$form_state) { + $settings = breakpoints_settings(); + $multiplier = $form_state['values']['multiplier']; + $multipliers = drupal_map_assoc($settings->multipliers); + if (isset($multipliers[$multiplier])) { + unset($multipliers[$multiplier]); + } + breakpoints_settings_save(array_values($multipliers)); + drupal_set_message(t('Multiplier %multiplier was deleted', array('%multiplier' => $multiplier))); + $form_state['redirect'] = 'admin/config/media/breakpoints/multipliers'; +} + +function breakpoints_admin_settings_form($form, &$form_state) { + $form['breakpoints_hide_overridden_breakpoints'] = array( + '#type' => 'checkbox', + '#description' => t('When overriding breakpoints defined by a theme, hide them on the overview page'), + '#title' => t('Hide overridden breakpoints'), + '#default_value' => variable_get('breakpoints_hide_overridden_breakpoints', 1), + ); + return system_settings_form($form); +} + +/** + * Duplicate group form. + */ +function breakpoints_admin_breakpoint_group_duplicate_form($form, &$form_state, $breakpoint_group_name) { + $form = array(); + + $src_group = breakpoints_breakpoint_group_load($breakpoint_group_name); + $form_state['#src_group'] = $src_group; + + $form['#attached']['css'][] = drupal_get_path('module', 'breakpoints') . '/css/breakpoints.admin.css'; + $form['name'] = array( + '#type' => 'textfield', + '#size' => '64', + '#title' => t('New group name'), + '#required' => TRUE, + '#default_value' => t('Duplicate of') . ' ' . $src_group->name, + ); + + $form['machine_name'] = array( + '#type' => 'machine_name', + '#size' => '64', + '#title' => t('Machine name'), + '#required' => TRUE, + '#default_value' => '', + '#machine_name' => array( + 'exists' => 'breakpoints_breakpoint_group_name_exists', + ), + ); + + // Buttons + $form['buttons'] = array( + '#type' => 'container', + ); + + // Submit button + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + $form['buttons']['cancellink'] = array( + '#type' => 'markup', + '#markup' => l( + t('Cancel'), 'admin/config/media/breakpoints/groups/' . $src_group->machine_name, + array( + 'attributes' => array('class' => array('breakpoints-group-operations-link', 'breakpoints-group-operations-cancel-link')), + ) + ), + ); + + return $form; +} + +/** + * Duplicate group form validate. + */ +function breakpoints_admin_breakpoint_group_duplicate_form_validate($form, &$form_state) { +} + +/** + * Duplicate group form submit. + */ +function breakpoints_admin_breakpoint_group_duplicate_form_submit($form, &$form_state) { + $machine_name = $form_state['values']['machine_name']; + $name = $form_state['values']['name']; + $src_group = $form_state['#src_group']; + if ($src_group) { + breakpoints_breakpoints_group_duplicate($src_group, $name, $machine_name); + // Clear the Ctools export API cache. + ctools_include('export'); + ctools_export_load_object_reset('breakpoint_group'); + menu_rebuild(); + $form_state['redirect'] = 'admin/config/media/breakpoints/groups/' . $machine_name; + drupal_set_message(t('The new group have been created')); + } +} diff --git a/sites/all/modules/breakpoints/breakpoints.info b/sites/all/modules/breakpoints/breakpoints.info new file mode 100644 index 0000000000000000000000000000000000000000..5b7deb635093a333f4cd5805f2ac31fb32c5ab03 --- /dev/null +++ b/sites/all/modules/breakpoints/breakpoints.info @@ -0,0 +1,13 @@ +name = Breakpoints +description = Manage breakpoints +core = 7.x +dependencies[] = ctools +files[] = breakpoints.module +files[] = breakpoints.test +configure = admin/config/media/breakpoints +; Information added by drupal.org packaging script on 2012-11-22 +version = "7.x-1.0" +core = "7.x" +project = "breakpoints" +datestamp = "1353614756" + diff --git a/sites/all/modules/breakpoints/breakpoints.install b/sites/all/modules/breakpoints/breakpoints.install new file mode 100644 index 0000000000000000000000000000000000000000..25c06f458309ac59ba70d15658bd2a13bb2c5cad --- /dev/null +++ b/sites/all/modules/breakpoints/breakpoints.install @@ -0,0 +1,185 @@ +<?php + +/** + * @file + * Breakpoints + */ + +/** + * Implements hook_schema(). + */ +function breakpoints_schema() { + $schema['breakpoints'] = array( + 'description' => 'Breakpoints', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier for a responsive images suffix', + 'no export' => TRUE, // Do not export database-only keys. + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The machine name of the breakpoint.', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The name of the breakpoint.', + ), + 'breakpoint' => array( + 'type' => 'varchar', + 'length' => 255, + 'description' => 'media query', + 'not null' => TRUE, + 'default' => '', + ), + 'source' => array( + 'type' => 'varchar', + 'length' => 255, + 'description' => 'name of theme, module', + 'not null' => TRUE, + 'default' => '', + ), + 'source_type' => array( + 'type' => 'varchar', + 'length' => 255, + 'description' => 'is breakpoint defined by theme, module or custom', + 'not null' => TRUE, + 'default' => '', + ), + 'status' => array( + 'type' => 'int', + 'description' => 'enabled or disabled', + 'not null' => TRUE, + 'default' => 1, + ), + 'weight' => array( + 'type' => 'int', + 'description' => 'weight', + 'not null' => TRUE, + 'default' => 0, + ), + 'multipliers' => array( + 'type' => 'blob', + 'description' => 'all enabled multipliers', + 'not null' => TRUE, + 'serialize' => TRUE, + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'machine_name' => array('machine_name'), + ), + // CTools exportable object definition + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'machine_name', + 'primary key' => 'id', + 'identifier' => 'breakpoint', + 'admin_title' => 'label', + 'default hook' => 'default_breakpoints', + 'api' => array( + 'owner' => 'breakpoints', + 'api' => 'default_breakpoints', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + ); + $schema['breakpoint_group'] = array( + 'description' => 'Breakpoint group', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier for a responsive images suffix', + 'no export' => TRUE, // Do not export database-only keys. + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The machine name of the breakpoint.', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The name of the breakpoint.', + ), + 'breakpoints' => array( + 'type' => 'blob', + 'description' => 'breakpoints', + 'not null' => TRUE, + 'serialize' => TRUE, + // we do not export options saved in this column, we export the fully loaded objects. + 'export callback' => 'breakpoint_group_export_breakpoints', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 255, + 'description' => 'theme, module or custom', + 'not null' => TRUE, + 'default' => '', + ), + 'overridden' => array( + 'type' => 'int', + 'description' => 'Boolean indicating if this group is overriden', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'machine_name' => array('machine_name'), + ), + // CTools exportable object definition + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'breakpoint group machine_name', + 'primary key' => 'id', + 'identifier' => 'breakpoint_group', + 'admin_title' => 'label', + 'default hook' => 'default_breakpoint_group', + 'export type string' => 'ctools_type', + 'export callback' => 'breakpoints_breakpoint_group_export', + 'api' => array( + 'owner' => 'breakpoints', + 'api' => 'default_breakpoint_group', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + ); + return $schema; +} + +/** + * Add the 'overridden' column to the breakpoint_group table. + */ +function breakpoints_update_7101(&$sandbox) { + db_add_field( + 'breakpoint_group', + 'overridden', + array( + 'type' => 'int', + 'description' => 'Boolean indicating if this group is overriden', + 'not null' => TRUE, + 'default' => 0, + ) + ); +} + +/** + * Add the '1x' multiplier to all breakpoints. + */ +function breakpoints_update_7102(&$sandbox) { + $breakpoints = breakpoints_breakpoint_load_all(); + foreach ($breakpoints as $breakpoint) { + breakpoints_breakpoint_save($breakpoint); + } +} diff --git a/sites/all/modules/breakpoints/breakpoints.module b/sites/all/modules/breakpoints/breakpoints.module new file mode 100644 index 0000000000000000000000000000000000000000..df4d7cca7d46ce8a512405f930e11e16aaf797c8 --- /dev/null +++ b/sites/all/modules/breakpoints/breakpoints.module @@ -0,0 +1,822 @@ +<?php + +/** + * @file + * Breakpoints + * @todo: provide button to reload breakpoints from theme + */ + +define('BREAKPOINTS_SOURCE_TYPE_THEME', 'theme'); +define('BREAKPOINTS_SOURCE_TYPE_MODULE', 'module'); +define('BREAKPOINTS_SOURCE_TYPE_CUSTOM', 'custom'); +define('BREAKPOINTS_GROUP', 'group'); + +/** + * Implements hook_permission(). + */ +function breakpoints_permission() { + return array( + 'administer breakpoints' => array( + 'title' => t('Administer Breakpoints'), + 'description' => t('Administer all breakpoints'), + ), + ); +} + +/** + * Implements hook_ctools_plugin_directory(). + */ +function breakpoints_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools' && $plugin == 'export_ui') { + return 'plugins/' . $plugin; + } +} + +/** + * Implements hook_ctools_plugin_api(). + */ +function breakpoints_ctools_plugin_api($owner, $api) { + return array('version' => 1); +} + +/** + * Implements hook_enable(). + * Import breakpoints from all enabled themes. + */ +function breakpoints_enable() { + $themes = list_themes(); + breakpoints_themes_enabled(array_keys($themes)); +} + +/** + * Implements hook_themes_enabled(); + * Import breakpoints from all new enabled themes. + * Do not use breakpoints_breakpoints_group_reload_from_theme as is clears the cache. + */ +function breakpoints_themes_enabled($theme_list) { + $themes = list_themes(); + foreach ($theme_list as $theme_key) { + if (isset($themes[$theme_key]->info['breakpoints'])) { + $weight = 0; + $theme_settings = $themes[$theme_key]->info['breakpoints']; + // Build a group for each theme + $breakpoint_group = breakpoints_breakpoint_group_empty_object(); + $breakpoint_group->machine_name = $theme_key; + $breakpoint_group->name = $themes[$theme_key]->info['name']; + $breakpoint_group->type = BREAKPOINTS_SOURCE_TYPE_THEME; + foreach ($theme_settings as $name => $media_query) { + $breakpoint = breakpoints_breakpoint_empty_object(); + $breakpoint->name = $name; + $breakpoint->breakpoint = $media_query; + $breakpoint->source = $theme_key; + $breakpoint->source_type = 'theme'; + $breakpoint->theme = ''; + $breakpoint->status = TRUE; + $breakpoint->weight = $weight++; + $breakpoint->machine_name = breakpoints_breakpoint_config_name($breakpoint); + breakpoints_breakpoint_save($breakpoint); + $breakpoint_group->breakpoints[] = $breakpoint->machine_name; + } + breakpoints_breakpoint_group_save($breakpoint_group); + $message = t('The breakpoints from theme %theme are imported and !grouplink.', array( + '%theme' => check_plain($themes[$theme_key]->info['name']), + '!grouplink' => l(t('a new group is created'), 'admin/config/media/breakpoints/groups/' . $theme_key), + )); + drupal_set_message($message, 'status'); + } + } + menu_rebuild(); +} + +/** + * Implements hook_themes_disabled(); + * Remove breakpoints from all disabled themes. + */ +function breakpoints_themes_disabled($theme_list) { + $themes = list_themes(); + foreach ($theme_list as $theme_key) { + $breakpoints = breakpoints_breakpoint_load_all_theme($theme_key); + foreach ($breakpoints as $breakpoint) { + breakpoints_breakpoint_delete($breakpoint, $theme_key); + } + breakpoints_breakpoint_group_delete_by_name($theme_key); + } + menu_rebuild(); +} + +/** + * Implements hook_menu(). + */ +function breakpoints_menu() { + $items = array(); + + // @todo: link to all breakpoints and a list of all groups + // cf theme settings page + $items['admin/config/media/breakpoints'] = array( + 'title' => 'Breakpoints', + 'description' => 'Manage breakpoints', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('breakpoints_admin_breakpoints'), + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + ); + + $items['admin/config/media/breakpoints/create_style'] = array( + 'title' => 'Add responsive style', + 'description' => 'Add a responsive image style', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('breakpoints_add_style_form'), + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 30, + ); + + $items['admin/config/media/breakpoints/multipliers'] = array( + 'title' => 'Multipliers', + 'description' => 'Manage multipliers', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('breakpoints_multipliers_form'), + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 20, + ); + + $items['admin/config/media/breakpoints/settings'] = array( + 'title' => 'settings', + 'description' => 'Manage breakpoint settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('breakpoints_admin_settings_form'), + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 30, + ); + + $items['admin/config/media/breakpoints/multipliers/%/delete'] = array( + 'title' => '', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('breakpoints_admin_multiplier_delete_form', 5), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + + $items['admin/config/media/breakpoints/groups'] = array( + 'title' => 'Groups', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 10, + ); + + $items['admin/config/media/breakpoints/groups/global'] = array( + 'title' => 'All breakpoints', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -1, + ); + + $items['admin/config/media/breakpoints/groups/add'] = array( + 'title' => 'Add a new group', + 'page arguments' => array('breakpoints_admin_breakpoint_group_edit_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 99, + ); + + $items['admin/config/media/breakpoints/groups/import'] = array( + 'title' => 'Import a new group', + 'page arguments' => array('breakpoints_admin_breakpoint_group_import_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 99, + ); + + $items['admin/config/media/breakpoints/groups/import-breakpoint'] = array( + 'title' => 'Import a new breakpoint', + 'page arguments' => array('breakpoints_admin_breakpoint_import_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 99, + ); + + $items['admin/config/media/breakpoints/%/%'] = array( + 'title' => '', + 'page callback' => 'breakpoints_admin_breakpoint_actions_page', + 'page arguments' => array('', 4, 5), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + + $breakpoint_groups = breakpoints_breakpoint_group_load_all(); + foreach ($breakpoint_groups as $breakpoint_group_name => $breakpoint_group) { + if (!empty($breakpoint_group->machine_name)) { + $items['admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name] = array( + 'title' => $breakpoint_group->name, + 'page arguments' => array('breakpoints_admin_breakpoints', $breakpoint_group->machine_name), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + $items['admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name . '/edit'] = array( + 'title' => 'Edit ' . $breakpoint_group->name, + 'page arguments' => array('breakpoints_admin_breakpoint_group_edit_form', $breakpoint_group->machine_name), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + $items['admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name . '/delete'] = array( + 'title' => 'Delete ' . $breakpoint_group->name, + 'page arguments' => array('breakpoints_admin_breakpoint_group_delete_form', $breakpoint_group->machine_name), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + + $items['admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name . '/export'] = array( + 'title' => 'Export ' . $breakpoint_group->name, + 'page arguments' => array('breakpoints_admin_breakpoint_group_export_form', $breakpoint_group->machine_name), + 'type' => MENU_LOCAL_ACTION, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + + $items['admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name . '/duplicate'] = array( + 'title' => 'Duplicate ' . $breakpoint_group->name, + 'page arguments' => array('breakpoints_admin_breakpoint_group_duplicate_form', $breakpoint_group->machine_name), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + + $items['admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name . '/%/%'] = array( + 'title' => '', + 'page arguments' => array('breakpoints_admin_breakpoint_actions_form', $breakpoint_group->machine_name, 6, 7), + 'type' => MENU_CALLBACK, + 'access arguments' => array('administer breakpoints'), + 'file' => 'breakpoints.admin.inc', + 'weight' => 15, + ); + } + } + + return $items; +} + +/** + * Load general settings. + */ +function breakpoints_settings() { + $config = new StdClass; + $config->multipliers = variable_get('breakpoints_multipliers', array('1x', '1.5x', '2x')); + return $config; +} + +/** + * Save general settings. + */ +function breakpoints_settings_save($multipliers) { + variable_set('breakpoints_multipliers', $multipliers); +} + +/** + * Sort breakpoints by weight. + */ +function _breakpoints_sort_by_weight($a, $b) { + if (isset($a->weight) && isset($b->weight)) { + if ($a->weight == $b->weight) { + if (isset($a->source_type) && $a->source_type == BREAKPOINTS_SOURCE_TYPE_CUSTOM) { + return -1; + } + if (isset($b->source_type) && $b->source_type == BREAKPOINTS_SOURCE_TYPE_CUSTOM) { + return 1; + } + return 0; + } + return ($a->weight < $b->weight) ? -1 : 1; + } + return 0; +} + +/** + * Sort breakpoints by weight. + */ +function _breakpoints_sort_by_weight_array($a, $b) { + return _breakpoints_sort_by_weight((object)$a, (object)$b); +} + +/** + * Construct config name. + */ +function breakpoints_breakpoint_config_name($breakpoints_breakpoint) { + if (is_string($breakpoints_breakpoint)) { + return $breakpoints_breakpoint; + } + else { + return drupal_strtolower('breakpoints' + . '.' . $breakpoints_breakpoint->source_type + . '.' . $breakpoints_breakpoint->source + . '.' . $breakpoints_breakpoint->name); + } +} + +/** + * Load a single breakpoint. + */ +function breakpoints_breakpoint_load($name, $source, $source_type) { + return breakpoints_breakpoint_load_by_fullkey(breakpoints_breakpoint_config_name($name, $source, $source_type)); +} + +/** + * Load a single breakpoint using the full config key. + */ +function breakpoints_breakpoint_load_by_fullkey($machine_name = NULL) { + // Use Ctools export API to fetch all presets from the DB as well as code. + ctools_include('export'); + if ($machine_name) { + $breakpoints = ctools_export_load_object('breakpoints', 'names', array($machine_name)); + $breakpoint = isset($breakpoints[$machine_name]) ? $breakpoints[$machine_name] : FALSE; + return $breakpoint; + } + else { + $breakpoints = ctools_export_load_object('breakpoints'); + return $breakpoints; + } +} + +/** + * Load all breakpoints. + */ +function breakpoints_breakpoint_load_all($theme_key = '') { + $breakpoints_user = breakpoints_breakpoint_load_all_custom(); + $breakpoints_module = breakpoints_breakpoint_load_all_module(); + $breakpoints_theme = breakpoints_breakpoint_load_all_theme($theme_key); + $breakpoints = array_merge($breakpoints_theme, $breakpoints_module, $breakpoints_user); + uasort($breakpoints, '_breakpoints_sort_by_weight'); + return $breakpoints; +} + +/** + * Load all enabled breakpoints. + */ +function breakpoints_breakpoint_load_all_active($theme_key = '') { + $breakpoints = breakpoints_breakpoint_load_all($theme_key); + $enabled = array(); + if (!empty($breakpoints)) { + foreach ($breakpoints as $breakpoint_name => $breakpoint) { + if ($breakpoint->status) { + $enabled[$breakpoint_name] = $breakpoint; + } + } + } + return $enabled; +} + +/** + * Load all breakpoints by source type. + */ +function _breakpoints_breakpoint_load_all_by_type($source_type, $source = '') { + $breakpoints = breakpoints_breakpoint_load_by_fullkey(); + foreach ($breakpoints as $machine_name => $breakpoint) { + if ($breakpoint->source_type != $source_type) { + unset($breakpoints[$machine_name]); + continue; + } + if ($source != '' && $breakpoint->source != $source) { + unset($breakpoints[$machine_name]); + } + } + return $breakpoints; +} + +/** + * Load all custom breakpoints. + */ +function breakpoints_breakpoint_load_all_custom() { + $breakpoints = _breakpoints_breakpoint_load_all_by_type(BREAKPOINTS_SOURCE_TYPE_CUSTOM); + return $breakpoints; +} + +/** + * Load all user defined breakpoints. + */ +function breakpoints_breakpoint_load_all_module() { + return _breakpoints_breakpoint_load_all_by_type(BREAKPOINTS_SOURCE_TYPE_MODULE); +} + +/** + * Load all breakpoints from the theme. + */ +function breakpoints_breakpoint_load_all_theme($theme_key = '') { + return _breakpoints_breakpoint_load_all_by_type(BREAKPOINTS_SOURCE_TYPE_THEME, $theme_key); +} + +/** + * Empty breakpoint object. + */ +function breakpoints_breakpoint_empty_object() { + return (object)breakpoints_breakpoint_empty_array(); +} + +/** + * Empty breakpoint array. + */ +function breakpoints_breakpoint_empty_array() { + $config = breakpoints_settings(); + return array( + 'name' => '', + 'machine_name' => '', + 'breakpoint' => '', + 'source' => '', + 'source_type' => '', + 'status' => TRUE, + 'weight' => 0, + 'multipliers' => array(), + ); +} + +/** + * Save a single breakpoint. + */ +function breakpoints_breakpoint_save(&$breakpoint) { + if (!isset($breakpoint->machine_name) || empty($breakpoint->machine_name)) { + $breakpoint->machine_name = breakpoints_breakpoint_config_name($breakpoint); + } + $update = (isset($breakpoint->id) && is_numeric($breakpoint->id)) ? array('id') : array(); + // Remove unused multipliers. + $breakpoint->multipliers = array_filter($breakpoint->multipliers); + // Add the '1x' multiplier. + $breakpoint->multipliers = array('1x' => '1x') + $breakpoint->multipliers; + $result = drupal_write_record('breakpoints', $breakpoint, $update); + // Reset CTools cache. + ctools_export_load_object_reset('breakpoints'); + return $result; +} + +/** + * Delete a single breakpoint. + */ +function breakpoints_breakpoint_delete($breakpoint) { + $name = breakpoints_breakpoint_config_name($breakpoint); + return breakpoints_breakpoint_delete_by_fullkey($name); +} + +/** + * Delete a single breakpoint. + */ +function breakpoints_breakpoint_delete_by_fullkey($key) { + if (!empty($key)) { + $sql = "DELETE FROM {breakpoints} where machine_name = :key"; + db_query($sql, array(':key' => $key)); + } + // Clear the Ctools export API cache. + ctools_include('export'); + ctools_export_load_object_reset('breakpoints'); +} + +/** + * Toggle status of a single breakpoint. + */ +function breakpoints_breakpoint_toggle_status($machine_name) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($machine_name); + if ($breakpoint) { + $breakpoint->status = !$breakpoint->status; + breakpoints_breakpoint_save($breakpoint); + } +} + +/** + * Check if a breakpoint name already exists. + */ +function breakpoints_breakpoint_name_exists($machine_name) { + $breakpoints = breakpoints_breakpoint_load_all_custom(); + $fullkey = 'custom.user.' . $machine_name; + return array_key_exists($fullkey, $breakpoints); +} + +/** + * Check if a breakpoint machine name already exists. + */ +function breakpoints_breakpoint_machine_name_exists($machine_name) { + // Just try to load the breakpoint object, we profit from ctool's cache mechanism, + // better that doing a query to the db every time this function is called. + return (bool)breakpoints_breakpoint_load_by_fullkey($machine_name); +} + +/** + * Empty breakpoint group object. + */ +function breakpoints_breakpoint_group_empty_object() { + return (object)breakpoints_breakpoint_group_empty_array(); +} + +/** + * Empty breakpoint group array. + */ +function breakpoints_breakpoint_group_empty_array() { + return array( + 'machine_name' => '', + 'name' => '', + 'breakpoints' => array(), + 'type' => 'custom', + ); +} + +/** + * Check if a group name already exists. + */ +function breakpoints_breakpoint_group_name_exists($machine_name) { + // Check for reserved words. + if ($machine_name == 'global' || $machine_name == 'add') { + return TRUE; + } + // Check if group name is used before. + $group_check = breakpoints_breakpoint_group_load($machine_name); + if ($group_check && isset($group_check->machine_name) && !empty($group_check->machine_name)) { + return TRUE; + } + return FALSE; +} + +/** + * Load all breakpoint groups. + */ +function breakpoints_breakpoint_group_load_all() { + return breakpoints_breakpoint_group_load(); +} + +/** + * Load a single breakpoint group. + */ +function breakpoints_breakpoint_group_load($name = NULL) { + // Use Ctools export API to fetch all presets from the DB as well as code. + ctools_include('export'); + if ($name) { + $groups = ctools_export_load_object('breakpoint_group', 'names', array($name)); + $group = isset($groups[$name]) ? $groups[$name] : FALSE; + return $group; + } + else { + $groups = ctools_export_load_object('breakpoint_group'); + return $groups; + } +} + +/** + * Validate a single breakpoint group. + */ +function breakpoints_breakpoint_group_validate($group) { + if (!is_object($group)) { + return FALSE; + } + foreach (array('machine_name', 'name', 'breakpoints', 'type') as $property) { + if (!property_exists($group, $property)) { + return FALSE; + } + } + return TRUE; +} + +/** + * Validate a single breakpoint. + */ +function breakpoints_breakpoint_validate($breakpoint) { + if (!is_object($breakpoint)) { + return FALSE; + } + foreach (array_keys(breakpoints_breakpoint_empty_array()) as $property) { + if (!property_exists($breakpoint, $property)) { + return FALSE; + } + } + return TRUE; +} + +/** + * Save a single breakpoint group. + */ +function breakpoints_breakpoint_group_save(&$breakpoint_group) { + $update = (isset($breakpoint_group->id) && is_numeric($breakpoint_group->id)) ? array('id') : array(); + $result = drupal_write_record('breakpoint_group', $breakpoint_group, $update); + // rebuild menu if we add a new group + if (empty ($update)) { + menu_rebuild(); + } + // Reset CTools cache. + ctools_export_load_object_reset('breakpoint_group'); + return $result; +} + +/** + * Export a single breakpoint group. + */ +function breakpoints_breakpoint_group_export($group, $indent = '') { + if (is_string($group)) { + $group = breakpoints_breakpoint_group_load($group); + } + $export = $indent . "// Breakpoints.\n" . $indent . '$breakpoints = array();' . "\n"; + if (isset($group->breakpoints)) { + foreach ($group->breakpoints as $breakpoint) { + if (!is_array($breakpoint) && !is_object($breakpoint)) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint); + } + $export .= $indent . '$breakpoints[] = \'' . $breakpoint->machine_name . '\';' . "\n"; + } + $export .= "\n"; + } + $export .= $indent . "// Breakpoint group.\n"; + $export .= ctools_export_object('breakpoint_group', $group, $indent); + return $export; +} + +/** + * Delete a single breakpoint group. + */ +function breakpoints_breakpoint_group_delete($breakpoint_group) { + $name = $breakpoint_group->machine_name; + return breakpoints_breakpoint_group_delete_by_fullkey($name); +} + +/** + * Delete a single breakpoint group by fullkey. + */ +function breakpoints_breakpoint_group_delete_by_name($machine_name) { + $name = $machine_name; + return breakpoints_breakpoint_group_delete_by_fullkey($name); +} + +/** + * Delete a single breakpoint group by fullkey. + */ +function breakpoints_breakpoint_group_delete_by_fullkey($key) { + if (!empty($key)) { + $sql = "DELETE FROM {breakpoint_group} where machine_name = :key"; + db_query($sql, array(':key' => $key)); + } + // Clear the Ctools export API cache. + ctools_include('export'); + ctools_export_load_object_reset('breakpoint_group'); + menu_rebuild(); +} + +/** + * Implements hook_theme(). + */ +function breakpoints_theme() { + return array( + 'breakpoints_admin_breakpoints_table' => array( + 'render element' => 'form', + 'theme_key' => NULL, + ), + 'breakpoints_multipliers_table_form' => array( + 'render element' => 'form', + 'theme_key' => NULL, + ) + ); +} + +/** + * Export callback. + * @see breakpoints_schema(), breakpoints_breakpoint_group_export() + */ +function breakpoint_group_export_breakpoints($object, $field, $value, $indent) { + $export = '$breakpoints'; + return $export; +} + +/** + * Reload the breakpoints as defined by the group. + */ +function breakpoints_breakpoints_group_reload(&$group) { + switch ($group->type) { + case BREAKPOINTS_SOURCE_TYPE_THEME: + // delete all breakpoints defined by this theme. + $breakpoints = breakpoints_breakpoint_load_all_theme($group->machine_name); + foreach ($breakpoints as $breakpoint) { + breakpoints_breakpoint_delete($breakpoint, $theme_key); + } + + // reload all breakpoints from theme.info. + $reloaded_group = breakpoints_breakpoints_group_reload_from_theme($group->machine_name); + $group->breakpoints = $reloaded_group->breakpoints; + breakpoints_breakpoint_group_save($group); + break; + } +} + +function breakpoints_breakpoints_group_reload_from_theme($theme_key) { + // Clear caches so theme.info is fresh. + system_rebuild_theme_data(); + drupal_theme_rebuild(); + + $themes = list_themes(); + if (isset($themes[$theme_key]->info['breakpoints'])) { + $weight = 0; + $theme_settings = $themes[$theme_key]->info['breakpoints']; + // Build a group for each theme + $breakpoint_group = breakpoints_breakpoint_group_empty_object(); + $breakpoint_group->machine_name = $theme_key; + $breakpoint_group->name = $themes[$theme_key]->info['name']; + $breakpoint_group->type = BREAKPOINTS_SOURCE_TYPE_THEME; + foreach ($theme_settings as $name => $media_query) { + $breakpoint = breakpoints_breakpoint_empty_object(); + $breakpoint->name = $name; + $breakpoint->breakpoint = $media_query; + $breakpoint->source = $theme_key; + $breakpoint->source_type = 'theme'; + $breakpoint->theme = ''; + $breakpoint->status = TRUE; + $breakpoint->weight = $weight++; + $breakpoint->machine_name = breakpoints_breakpoint_config_name($breakpoint); + breakpoints_breakpoint_save($breakpoint); + $breakpoint_group->breakpoints[] = $breakpoint->machine_name; + } + return $breakpoint_group; + } +} + +/** + * Revert the breakpoints of a group. + */ +function breakpoints_breakpoints_group_revert(&$group) { + breakpoints_breakpoints_group_reload($group); + $group->overridden = 0; + breakpoints_breakpoint_group_save($group); +} + +/** + * Duplicate a group. + */ +function breakpoints_breakpoints_group_duplicate($group, $new_name, $new_machine_name) { + $new_group = breakpoints_breakpoint_group_empty_object(); + $new_group->machine_name = $new_machine_name; + $new_group->name = $new_name; + $new_group->type = BREAKPOINTS_SOURCE_TYPE_CUSTOM; + $new_group->breakpoints = $group->breakpoints; + breakpoints_breakpoint_group_save($new_group); + return $new_group; +} + +/** + * Override the breakpoints of a group. + */ +function breakpoints_breakpoints_group_override($group) { + foreach ($group->breakpoints as $key => $breakpoint) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint); + $old_breakpoint = clone $breakpoint; + if ($breakpoint->source_type == BREAKPOINTS_SOURCE_TYPE_THEME && $breakpoint->source == $group->machine_name) { + unset($breakpoint->id); + $breakpoint->machine_name = 'custom.' . $breakpoint->source . '.' . str_replace('-', '_', drupal_clean_css_identifier($breakpoint->name)); + $breakpoint->source_type = BREAKPOINTS_SOURCE_TYPE_CUSTOM; + + // make sure it doesn't already exists. + if (breakpoints_breakpoint_load_by_fullkey($breakpoint->machine_name) === FALSE) { + breakpoints_breakpoint_save($breakpoint); + } + + // Add to the group and delete old breakpoint. + $group->breakpoints[$key] = $breakpoint->machine_name; + breakpoints_breakpoint_delete($old_breakpoint, $group->machine_name); + } + } + $group->overridden = 1; + breakpoints_breakpoint_group_save($group); +} + +/** + * Export breakpoints ready for theme.info inclusion. + */ +function breakpoints_breakpoints_group_exporttotheme(&$group) { + $export = array(); + foreach ($group->breakpoints as $breakpoint_name) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint_name); + if ($breakpoint && $breakpoint->status) { + $export[$breakpoint->name] = $breakpoint->breakpoint; + } + } + return $export; +} + +/** + * array_filter callback. + */ +function _breakpoints_filter_styles($var) { + static $exists = NULL; + if (is_null($exists)) { + $exists = module_exists('resp_img'); + } + if (!$exists) { + return TRUE; + } + return strpos(is_array($var) ? $var['name'] : $var, RESP_IMG_STYLE_PREFIX) !== 0; +} diff --git a/sites/all/modules/breakpoints/breakpoints.test b/sites/all/modules/breakpoints/breakpoints.test new file mode 100644 index 0000000000000000000000000000000000000000..b75a479e870635d2d37bcf7b512e860a21d95d16 --- /dev/null +++ b/sites/all/modules/breakpoints/breakpoints.test @@ -0,0 +1,913 @@ +<?php + +/** + * @file + * Tests for breakpoints.module + */ + +/** + * Base class for Breakpoint tests. + */ +abstract class BreakpointsTestCase extends DrupalWebTestCase { + function setUp() { + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + array_unshift($modules, 'breakpoints'); + parent::setUp($modules); + } + + /** + * Verify that a breakpoint is properly stored. + */ + function verifyBreakpoint($breakpoint, $in_database = TRUE) { + $t_args = array('%breakpoint' => $breakpoint->name); + $properties = array('name', 'breakpoint', 'source', 'source_type', 'status', 'weight', 'multipliers'); + if ($in_database) { + $properties[] = 'id'; + } + $assert_group = t('Breakpoints API'); + // Verify text format database record. + $db_breakpoint = db_select('breakpoints', 'b') + ->fields('b') + ->condition('machine_name', $breakpoint->machine_name) + ->execute() + ->fetchObject(); + $db_breakpoint->multipliers = unserialize($db_breakpoint->multipliers); + foreach ($properties as $property) { + $this->assertEqual($db_breakpoint->{$property}, $breakpoint->{$property}, t('Database: Proper ' . $property . ' for breakpoint %breakpoint.', $t_args), $assert_group); + } + + // Verify breakpoints_breakpoint_load_by_fullkey(). + $load_breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint->machine_name); + foreach ($properties as $property) { + $this->assertEqual($load_breakpoint->{$property}, $breakpoint->{$property}, t('breakpoints_breakpoint_load_by_fullkey: Proper ' . $property . ' for breakpoint %breakpoint.', $t_args), $assert_group); + } + } +} + +/** + * Tests for breakpoints CRUD operations. + */ +class BreakpointsCRUDTestCase extends BreakpointsTestCase { + + public static function getInfo() { + return array( + 'name' => 'Breakpoints CRUD operations', + 'description' => 'Test creation, loading, updating, deleting of breakpoints.', + 'group' => 'Breakpoints', + ); + } + + /** + * Test CRUD operations for breakpoints. + */ + function testBreakpointsCRUD() { + // Add a breakpoint with minimum data only. + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->name = 'Custom'; + $breakpoint->breakpoint = '(min-width: 600px)'; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = 0; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + breakpoints_breakpoint_save($breakpoint); + $this->verifyBreakpoint($breakpoint); + + // Update the breakpoint. + $breakpoint->weight = 1; + $breakpoint->multipliers['2x'] = 1; + breakpoints_breakpoint_save($breakpoint); + $this->verifyBreakpoint($breakpoint); + + // Disable the breakpoint. + $breakpoint->status = 0; + breakpoints_breakpoint_save($breakpoint); + $this->verifyBreakpoint($breakpoint); + $breakpoints = breakpoints_breakpoint_load_all_active(); + $this->assertFalse(isset($breakpoints[$breakpoint->machine_name]), t('breakpoints_breakpoint_load_all_active: Disabled breakpoints aren\'t loaded.'), t('Breakpoints API')); + + // Delete the breakpoint. + breakpoints_breakpoint_delete($breakpoint); + $db_breakpoint = db_select('breakpoints', 'b')->fields('b')->condition('machine_name', $breakpoint->machine_name)->execute()->fetchObject(); + $this->assertFalse($db_breakpoint, t('Database: Deleted breakpoint no longer exists'), t('Breakpoints API')); + $this->assertFalse(breakpoints_breakpoint_load_by_fullkey($breakpoint->machine_name), t('breakpoints_breakpoint_load_by_fullkey: Loading a deleted breakpoint returns false.'), t('Breakpoints API')); + } +} + +/** + * Tests for breakpoints admin interface. + */ +class BreakpointsAdminTestCase extends BreakpointsTestCase { + + public static function getInfo() { + return array( + 'name' => 'Breakpoints administration functionality', + 'description' => 'Thoroughly test the administrative interface of the breakpoints module.', + 'group' => 'Breakpoints', + ); + } + + function setUp() { + parent::setUp(); + + // Create user. + $this->admin_user = $this->drupalCreateUser(array( + 'administer breakpoints', + )); + + $this->drupalLogin($this->admin_user); + } + + /** + * Test breakpoint administration functionality + */ + function testBreakpointAdmin() { + // Add breakpoint. + $this->drupalGet('admin/config/media/breakpoints'); + $name = $this->randomName(); + $mediaquery = '(min-width: 600px)'; + $edit = array( + 'breakpoints[new][name]' => $name, + 'breakpoints[new][machine_name]' => drupal_strtolower($name), + 'breakpoints[new][breakpoint]' => $mediaquery, + ); + + $this->drupalPost(NULL, $edit, t('Save')); + + $machine_name = BREAKPOINTS_SOURCE_TYPE_CUSTOM . '.user.' . drupal_strtolower($name); + // Verify the breakpoint was saved and verify default weight of the breakpoint. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertFieldByName("breakpoints[$machine_name][weight]", 0, t('Breakpoint weight was saved.')); + + // Change the weight of the breakpoint. + $edit = array( + "breakpoints[$machine_name][weight]" => 5, + ); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertFieldByName("breakpoints[$machine_name][weight]", 5, t('Breakpoint weight was saved.')); + + // Submit the form. + $this->drupalGet('admin/config/media/breakpoints'); + $this->drupalPost(NULL, array(), t('Save')); + + // Verify that the custom weight of the breakpoint has been retained. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertFieldByName("breakpoints[$machine_name][weight]", 5, t('Breakpoint weight was retained.')); + + // Change the multipliers of the breakpoint. + $edit = array( + "breakpoints[$machine_name][multipliers][1.5x]" => "1.5x", + ); + $this->drupalPost(NULL, $edit, t('Save')); + $id = drupal_clean_css_identifier('edit-breakpoints-' . $machine_name . '-multipliers-'); + $this->assertFieldChecked($id . '15x', t('Breakpoint multipliers were saved.')); + $this->assertNoFieldChecked($id . '2x', t('Breakpoint multipliers were saved.')); + + // Submit the form. + $this->drupalGet('admin/config/media/breakpoints'); + $this->drupalPost(NULL, array(), t('Save')); + + // Verify that the custom weight of the breakpoint has been retained. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertFieldChecked($id . '15x', t('Breakpoint multipliers were retained.')); + $this->assertNoFieldChecked($id . '2x', t('Breakpoint multipliers were retained.')); + + // Disable breakpoint. + $this->assertLinkByHref('admin/config/media/breakpoints/disable/' . $machine_name); + $this->drupalGet('admin/config/media/breakpoints/disable/' . $machine_name); + $this->drupalPost(NULL, array(), t('Confirm')); + + // Verify that the breakpoint is disabled. + $this->assertLinkByHref('admin/config/media/breakpoints/enable/' . $machine_name, 0, t('Breakpoint was disabled.')); + + // Attempt to create a breakpoint with the same machine name as the disabled + // breakpoint but with a different human readable name. + $edit = array( + 'breakpoints[new][name]' => 'New Breakpoint', + 'breakpoints[new][machine_name]' => drupal_strtolower($name), + 'breakpoints[new][breakpoint]' => $mediaquery, + 'breakpoints[new][multipliers][1.5x]' => 0, + 'breakpoints[new][multipliers][2x]' => 0, + ); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertText('The machine-readable name is already in use. It must be unique.'); + + // Delete breakpoint. + $this->assertLinkByHref('admin/config/media/breakpoints/delete/' . $machine_name); + $this->drupalGet('admin/config/media/breakpoints/delete/' . $machine_name); + $this->drupalPost(NULL, array(), t('Confirm')); + + // Verify that deleted breakpoint no longer exists. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertNoFieldByName('breakpoints[' . $machine_name . '][name]', '', t('Deleted breakpoint no longer exists')); + } + + /** + * Test breakpoint export/import functionality. + */ + function testBreakpointExportImport() { + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->machine_name = 'custom.user.test'; + $breakpoint->name = 'test'; + $breakpoint->breakpoint = '(min-width: 600px)'; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = 0; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + + // Import a breakpoint; + $importstring = array(); + $importstring[] = '$breakpoint = new stdClass();'; + $importstring[] = '$breakpoint->disabled = FALSE; /* Edit this to true to make a default breakpoint disabled initially */'; + $importstring[] = '$breakpoint->api_version = 1;'; + $importstring[] = '$breakpoint->machine_name = \'custom.user.test\';'; + $importstring[] = '$breakpoint->name = \'test\';'; + $importstring[] = '$breakpoint->breakpoint = \'(min-width: 600px)\';'; + $importstring[] = '$breakpoint->source = \'user\';'; + $importstring[] = '$breakpoint->source_type = \'custom\';'; + $importstring[] = '$breakpoint->status = 1;'; + $importstring[] = '$breakpoint->weight = 0;'; + $importstring[] = '$breakpoint->multipliers = array('; + $importstring[] = ' \'1.5x\' => 0,'; + $importstring[] = ' \'2x\' => 0,'; + $importstring[] = ');'; + + $this->drupalGet('admin/config/media/breakpoints/groups/import-breakpoint'); + $edit = array( + "import" => implode("\n", $importstring), + ); + $this->drupalPost(NULL, $edit, t('Import')); + + // Verify the breakpoint was imported. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertField('breakpoints[' . $breakpoint->machine_name . '][name]', t('Breakpoint imported correctly.')); + + // Verify the breakpoint is in the database, is loadable and has the correct data. + $this->verifyBreakpoint($breakpoint, FALSE); + + // Verify the breakpoint exports correctly. + $this->drupalGet('admin/config/media/breakpoints/export/' . $breakpoint->machine_name); + foreach ($importstring as $importline) { + $importline = trim($importline); + if (!empty($importline)) { + // Text in a textarea is htmlencoded. + $this->assertRaw(check_plain($importline)); + } + } + } +} + +/** + * Base class for Breakpoint Group tests. + */ +abstract class BreakpointGroupTestCase extends DrupalWebTestCase { + function setUp() { + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + array_unshift($modules, 'breakpoints'); + parent::setUp($modules); + } + + /** + * Verify that a breakpoint is properly stored. + */ + function verifyBreakpointGroup($group, $in_database = TRUE) { + $t_args = array('%group' => $group->name); + $properties = array('name', 'machine_name', 'breakpoints'); + if ($in_database) { + $properties[] = 'id'; + } + $assert_group = t('Breakpoints API'); + // Verify text format database record. + $db_group = db_select('breakpoint_group', 'bg') + ->fields('bg') + ->condition('machine_name', $group->machine_name) + ->execute() + ->fetchObject(); + $db_group->breakpoints = unserialize($db_group->breakpoints); + foreach ($properties as $property) { + $this->assertEqual($db_group->{$property}, $group->{$property}, t('Database: Proper ' . $property . ' for breakpoint group %group.', $t_args), $assert_group); + } + + // Verify breakpoints_breakpoint_group_load(). + $load_group = breakpoints_breakpoint_group_load($group->machine_name); + foreach ($properties as $property) { + $this->assertEqual($load_group->{$property}, $group->{$property}, t('breakpoints_breakpoint_group_load: Proper ' . $property . ' for breakpoint group %group.', $t_args), $assert_group); + } + } +} + +/** + * Tests for breakpoint group CRUD operations. + */ +class BreakpointGroupCRUDTestCase extends BreakpointGroupTestCase { + + public static function getInfo() { + return array( + 'name' => 'Breakpoint Group CRUD operations', + 'description' => 'Test creation, loading, updating, deleting of breakpoint groups.', + 'group' => 'Breakpoints', + ); + } + + /** + * Test CRUD operations for breakpoint groups. + */ + function testBreakpointGroupCRUD() { + // Add breakpoints. + $breakpoints = array(); + for ($i = 0; $i <= 3; $i++) { + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->name = $this->randomName(); + $width = ($i + 1) * 200; + $breakpoint->breakpoint = "(min-width: {$width}px)"; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = $i; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + breakpoints_breakpoint_save($breakpoint); + $breakpoints[$breakpoint->machine_name] = $breakpoint; + } + // Add a breakpoint group with minimum data only. + $group = new stdClass(); + $group->name = $this->randomName(); + $group->machine_name = drupal_strtolower($group->name); + $group->breakpoints = array(); + breakpoints_breakpoint_group_save($group); + $this->verifyBreakpointGroup($group); + + // Update the breakpoint group. + $group->breakpoints = array_keys($breakpoints); + breakpoints_breakpoint_group_save($group); + $this->verifyBreakpointGroup($group); + + // Delete the breakpoint group. + breakpoints_breakpoint_group_delete($group); + $db_group = db_select('breakpoint_group', 'bg')->fields('bg')->condition('machine_name', $group->machine_name)->execute()->fetchObject(); + $this->assertFalse($db_group, t('Database: Deleted breakpoint group no longer exists'), t('Breakpoints API')); + $this->assertFalse(breakpoints_breakpoint_group_load($group->machine_name), t('breakpoints_breakpoint_group_load: Loading a deleted breakpoint group returns false.'), t('Breakpoints API')); + } +} + +/** + * Tests for breakpoint groups admin interface. + */ +class BreakpointGroupAdminTestCase extends BreakpointGroupTestCase { + + public static function getInfo() { + return array( + 'name' => 'Breakpoint Group administration functionality', + 'description' => 'Thoroughly test the administrative interface of the breakpoints module.', + 'group' => 'Breakpoints', + ); + } + + function setUp() { + parent::setUp(); + + // Create user. + $this->admin_user = $this->drupalCreateUser(array( + 'administer breakpoints', + )); + + $this->drupalLogin($this->admin_user); + } + + /** + * Test breakpoint administration functionality + */ + function testBreakpointGroupAdmin() { + // Add breakpoints. + $breakpoints = array(); + for ($i = 0; $i <= 3; $i++) { + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->name = $this->randomName(); + $width = ($i + 1) * 200; + $breakpoint->breakpoint = "(min-width: {$width}px)"; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = $i; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + breakpoints_breakpoint_save($breakpoint); + $breakpoints[$breakpoint->machine_name] = $breakpoint; + } + // Add breakpoint group. + $this->drupalGet('admin/config/media/breakpoints/groups/add'); + $name = $this->randomName(); + $machine_name = drupal_strtolower($name); + $breakpoint = reset($breakpoints); + $edit = array( + 'name' => $name, + 'machine_name' => $machine_name, + 'breakpoints[' . $breakpoint->machine_name . ']' => $breakpoint->machine_name, + ); + + $this->drupalPost(NULL, $edit, t('Save')); + + // Verify the breakpoint was saved. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name); + $this->assertResponse(200, t('Breakpoint group was saved.')); + + // Verify the breakpoint was attached to the group. + $this->assertField('breakpoints[' . $breakpoint->machine_name . '][name]', t('The Breakpoint was added.')); + + // Add breakpoints to the breakpoint group. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name . '/edit'); + $edit = array(); + foreach ($breakpoints as $key => $breakpoint) { + $edit['breakpoints[' . $key . ']'] = $key; + } + $this->drupalPost(NULL, $edit, t('Save')); + + // Verify the breakpoints were attached to the group. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name); + foreach ($breakpoints as $key => $breakpoint) { + $this->assertField('breakpoints[' . $key . '][name]', t('The Breakpoint was added.')); + } + + // Change the order breakpoints of the breakpoints within the breakpoint group. + $breakpoint = end($breakpoints); + $edit = array( + "breakpoints[{$breakpoint->machine_name}][weight]" => 0, + ); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertFieldByName("breakpoints[{$breakpoint->machine_name}][weight]", 0, t('Breakpoint weight was saved.')); + + // Submit the form. + $this->drupalGet('admin/config/media/breakpoints'); + $this->drupalPost(NULL, array(), t('Save')); + + // Verify that the custom weight of the breakpoint has been retained. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name); + $this->assertFieldByName("breakpoints[{$breakpoint->machine_name}][weight]", 0, t('Breakpoint weight was retained.')); + + // Verify that the weight has only changed within the group. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertFieldByName("breakpoints[{$breakpoint->machine_name}][weight]", $breakpoint->weight, t('Breakpoint weight has only changed within the group.')); + + // Change the multipliers of the breakpoint within the group. + $edit = array( + "breakpoints[{$breakpoint->machine_name}][multipliers][1.5x]" => "1.5x", + ); + $this->drupalPost(NULL, $edit, t('Save')); + $id = drupal_clean_css_identifier('edit-breakpoints-' . $breakpoint->machine_name . '-multipliers-'); + $this->assertFieldChecked($id . '15x', t('Breakpoint multipliers were saved.')); + $this->assertNoFieldChecked($id . '2x', t('Breakpoint multipliers were saved.')); + + // Submit the form. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name); + $this->drupalPost(NULL, array(), t('Save')); + + // Verify that the multipliers of the breakpoint has been retained. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name); + $this->assertFieldChecked($id . '15x', t('Breakpoint multipliers were retained.')); + $this->assertNoFieldChecked($id . '2x', t('Breakpoint multipliers were retained.')); + + // Verify that the multipliers only changed within the group. + $this->drupalGet('admin/config/media/breakpoints'); + $this->assertFieldChecked($id . '15x', t('Breakpoint multipliers were retained.')); + $this->assertNoFieldChecked($id . '2x', t('Breakpoint multipliers were retained.')); + + // Attempt to create a breakpoint group of the same machine name as the disabled + // breakpoint but with a different human readable name. + // Add breakpoint group. + $this->drupalGet('admin/config/media/breakpoints/groups/add'); + $breakpoint = reset($breakpoints); + $edit = array( + 'name' => $this->randomName(), + 'machine_name' => $machine_name, + 'breakpoints[' . $breakpoint->machine_name . ']' => $breakpoint->machine_name, + ); + + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertText('The machine-readable name is already in use. It must be unique.'); + + // Delete breakpoint. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name . '/delete'); + $this->drupalPost(NULL, array(), t('Confirm')); + + // Verify that deleted breakpoint no longer exists. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $machine_name); + $this->assertResponse(404, t('Breakpoint group was deleted.')); + } + + /** + * Test breakpoint group export/import functionality. + */ + function testBreakpointGroupExportImport() { + /** + * Breakpoints. + */ + $breakpoints = array(); + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->machine_name = 'custom.user.mobile'; + $breakpoint->name = 'mobile'; + $breakpoint->breakpoint = '(min-width: 0px)'; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = 4; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + + $breakpoints[$breakpoint->machine_name] = $breakpoint; + + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->machine_name = 'custom.user.narrow'; + $breakpoint->name = 'narrow'; + $breakpoint->breakpoint = '(min-width: 560px)'; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = 5; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + + $breakpoints[$breakpoint->machine_name] = $breakpoint; + + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->machine_name = 'custom.user.wide'; + $breakpoint->name = 'wide'; + $breakpoint->breakpoint = '(min-width: 851px)'; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = 6; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + + $breakpoints[$breakpoint->machine_name] = $breakpoint; + + $breakpoint = new stdClass(); + $breakpoint->disabled = FALSE; + $breakpoint->api_version = 1; + $breakpoint->machine_name = 'custom.user.tv'; + $breakpoint->name = 'tv'; + $breakpoint->breakpoint = 'only screen and (min-width: 3456px)'; + $breakpoint->source = 'user'; + $breakpoint->source_type = 'custom'; + $breakpoint->status = 1; + $breakpoint->weight = 7; + $breakpoint->multipliers = array( + '1.5x' => 0, + '2x' => 0, + ); + + $breakpoints[$breakpoint->machine_name] = $breakpoint; + + /** + * Breakpoint group. + */ + $breakpoint_group = new stdClass(); + $breakpoint_group->disabled = FALSE; /* Edit this to true to make a default breakpoint_group disabled initially */ + $breakpoint_group->api_version = 1; + $breakpoint_group->machine_name = 'customgroup'; + $breakpoint_group->name = 'Customgroup'; + $breakpoint_group->breakpoints = array_keys($breakpoints); + $breakpoint_group->type = 'custom'; + $breakpoint_group->overridden = 0; + + $importstring = array(); + $importstring[] = '/**'; + $importstring[] = ' * Breakpoints.'; + $importstring[] = ' */'; + $importstring[] = '$breakpoints = array();'; + $importstring[] = '$breakpoint = new stdClass();'; + $importstring[] = '$breakpoint->disabled = FALSE; /* Edit this to true to make a default breakpoint disabled initially */'; + $importstring[] = '$breakpoint->api_version = 1;'; + $importstring[] = '$breakpoint->machine_name = \'custom.user.mobile\';'; + $importstring[] = '$breakpoint->name = \'mobile\';'; + $importstring[] = '$breakpoint->breakpoint = \'(min-width: 0px)\';'; + $importstring[] = '$breakpoint->source = \'user\';'; + $importstring[] = '$breakpoint->source_type = \'custom\';'; + $importstring[] = '$breakpoint->status = 1;'; + $importstring[] = '$breakpoint->weight = 4;'; + $importstring[] = '$breakpoint->multipliers = array('; + $importstring[] = ' \'1.5x\' => 0,'; + $importstring[] = ' \'2x\' => 0,'; + $importstring[] = ');'; + $importstring[] = ''; + $importstring[] = '$breakpoints[] = $breakpoint;'; + $importstring[] = ''; + $importstring[] = '$breakpoint = new stdClass();'; + $importstring[] = '$breakpoint->disabled = FALSE; /* Edit this to true to make a default breakpoint disabled initially */'; + $importstring[] = '$breakpoint->api_version = 1;'; + $importstring[] = '$breakpoint->machine_name = \'custom.user.narrow\';'; + $importstring[] = '$breakpoint->name = \'narrow\';'; + $importstring[] = '$breakpoint->breakpoint = \'(min-width: 560px)\';'; + $importstring[] = '$breakpoint->source = \'user\';'; + $importstring[] = '$breakpoint->source_type = \'custom\';'; + $importstring[] = '$breakpoint->status = 1;'; + $importstring[] = '$breakpoint->weight = 5;'; + $importstring[] = '$breakpoint->multipliers = array('; + $importstring[] = ' \'1.5x\' => 0,'; + $importstring[] = ' \'2x\' => 0,'; + $importstring[] = ');'; + $importstring[] = ''; + $importstring[] = '$breakpoints[] = $breakpoint;'; + $importstring[] = ''; + $importstring[] = '$breakpoint = new stdClass();'; + $importstring[] = '$breakpoint->disabled = FALSE; /* Edit this to true to make a default breakpoint disabled initially */'; + $importstring[] = '$breakpoint->api_version = 1;'; + $importstring[] = '$breakpoint->machine_name = \'custom.user.wide\';'; + $importstring[] = '$breakpoint->name = \'wide\';'; + $importstring[] = '$breakpoint->breakpoint = \'(min-width: 851px)\';'; + $importstring[] = '$breakpoint->source = \'user\';'; + $importstring[] = '$breakpoint->source_type = \'custom\';'; + $importstring[] = '$breakpoint->status = 1;'; + $importstring[] = '$breakpoint->weight = 6;'; + $importstring[] = '$breakpoint->multipliers = array('; + $importstring[] = ' \'1.5x\' => 0,'; + $importstring[] = ' \'2x\' => 0,'; + $importstring[] = ');'; + $importstring[] = ''; + $importstring[] = '$breakpoints[] = $breakpoint;'; + $importstring[] = ''; + $importstring[] = '$breakpoint = new stdClass();'; + $importstring[] = '$breakpoint->disabled = FALSE; /* Edit this to true to make a default breakpoint disabled initially */'; + $importstring[] = '$breakpoint->api_version = 1;'; + $importstring[] = '$breakpoint->machine_name = \'custom.user.tv\';'; + $importstring[] = '$breakpoint->name = \'tv\';'; + $importstring[] = '$breakpoint->breakpoint = \'only screen and (min-width: 3456px)\';'; + $importstring[] = '$breakpoint->source = \'user\';'; + $importstring[] = '$breakpoint->source_type = \'custom\';'; + $importstring[] = '$breakpoint->status = 1;'; + $importstring[] = '$breakpoint->weight = 7;'; + $importstring[] = '$breakpoint->multipliers = array('; + $importstring[] = ' \'1.5x\' => 0,'; + $importstring[] = ' \'2x\' => 0,'; + $importstring[] = ');'; + $importstring[] = ''; + $importstring[] = '$breakpoints[] = $breakpoint;'; + $importstring[] = ''; + $importstring[] = '/**'; + $importstring[] = ' * Breakpoint group.'; + $importstring[] = ' */'; + $importstring[] = '$breakpoint_group = new stdClass();'; + $importstring[] = '$breakpoint_group->disabled = FALSE; /* Edit this to true to make a default breakpoint_group disabled initially */'; + $importstring[] = '$breakpoint_group->api_version = 1;'; + $importstring[] = '$breakpoint_group->machine_name = \'customgroup\';'; + $importstring[] = '$breakpoint_group->name = \'Customgroup\';'; + $importstring[] = '$breakpoint_group->breakpoints = $breakpoints;'; + $importstring[] = '$breakpoint_group->type = \'custom\';'; + $importstring[] = '$breakpoint_group->overridden = 0;'; + + $this->drupalGet('admin/config/media/breakpoints/groups/import'); + $edit = array( + "import" => implode("\n", $importstring), + ); + $this->drupalPost(NULL, $edit, t('Import')); + + // Verify the breakpoint group was imported. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name); + $this->assertResponse(200, t('Breakpoint group imported correctly')); + + // Verify the breakpoint group is in the database, is loadable and has the correct data. + $this->verifyBreakpointGroup($breakpoint_group, FALSE); + + // Verify the breakpoint group exports correctly. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name . '/export'); + foreach ($importstring as $importline) { + $importline = trim($importline); + if (!empty($importline)) { + // Text in a textarea is htmlencoded. + $this->assertRaw(check_plain($importline)); + } + } + } +} + +/** + * Test breakpoints provided by themes. + */ +class BreakpointsThemeTestCase extends BreakpointGroupTestCase { + public static function getInfo() { + return array( + 'name' => 'Breakpoint Theme functionality', + 'description' => 'Thoroughly test the breakpoints provided by a theme.', + 'group' => 'Breakpoints', + ); + } + + public function setUp() { + parent::setUp('breakpoints_theme_test'); + theme_enable(array('breakpoints_test_theme')); + // Create user. + $this->admin_user = $this->drupalCreateUser(array( + 'administer breakpoints', + )); + + $this->drupalLogin($this->admin_user); + } + + /** + * Test the breakpoints provided by a theme. + */ + public function testThemeBreakpoints() { + // Verify the breakpoint group for breakpoints_test_theme was created. + $breakpoint_group = new stdClass(); + $breakpoint_group->disabled = FALSE; /* Edit this to true to make a default breakpoint_group disabled initially */ + $breakpoint_group->api_version = 1; + $breakpoint_group->machine_name = 'breakpoints_test_theme'; + $breakpoint_group->name = 'Breakpoints test theme'; + $breakpoint_group->breakpoints = array( + 'breakpoints.theme.breakpoints_test_theme.mobile', + 'breakpoints.theme.breakpoints_test_theme.narrow', + 'breakpoints.theme.breakpoints_test_theme.wide', + 'breakpoints.theme.breakpoints_test_theme.tv', + ); + $breakpoint_group->type = 'theme'; + $breakpoint_group->overridden = 0; + $this->verifyBreakpointGroup($breakpoint_group, FALSE); + + // Override the breakpoints. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name); + $this->drupalPost(NULL, array(), t('Override theme breakpoints')); + + // Clear CTools cache, since drupalGet and drupalPost are different requests than the request + // this test is running in, the group object is still in the static cache, so we need to clear + // it manually. + ctools_export_load_object_reset('breakpoint_group'); + + // Verify the group is overridden. + $breakpoint_group->breakpoints = array( + 'custom.breakpoints_test_theme.mobile', + 'custom.breakpoints_test_theme.narrow', + 'custom.breakpoints_test_theme.wide', + 'custom.breakpoints_test_theme.tv', + ); + $breakpoint_group->overridden = 1; + $this->verifyBreakpointGroup($breakpoint_group, FALSE); + + // Verify there is no override button for this group anymore. + $this->drupalGet('admin/config/media/breakpoints/groups/' . $breakpoint_group->machine_name); + $this->assertNoFieldById('edit-override'); + } +} + +/** + * Test breakpoint multipliers. + */ +class BreakpointMultipliersTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Breakpoint Multiplier functionality', + 'description' => 'Thoroughly test the breakpoint multiplier functionality (CRUD).', + 'group' => 'Breakpoints', + ); + } + + public function setUp() { + parent::setUp('breakpoints', 'breakpoints_theme_test'); + // Enable our test theme so we have breakpoints to test on. + theme_enable(array('breakpoints_test_theme')); + // Create user. + $this->admin_user = $this->drupalCreateUser(array( + 'administer breakpoints', + )); + + $this->drupalLogin($this->admin_user); + } + + /** + * Test breakpoints multipliers functionality. + */ + public function testBreakpointMultipliers() { + // Verify the default multipliers are visible. + $this->drupalGet('admin/config/media/breakpoints/multipliers'); + $settings = breakpoints_settings(); + foreach ($settings->multipliers as $multiplier) { + $this->assertRaw($multiplier, t('Default multiplier %multiplier found', array('%multiplier' => $multiplier))); + if ($multiplier != '1x') { + $this->assertFieldByName('multipliers[' . $multiplier . ']', $multiplier); + } + } + + // Verify the '1x' multiplier can't be deleted. + $this->drupalGet('admin/config/media/breakpoints/multipliers/1x/delete'); + $this->assertText(t('Multiplier 1x can not be deleted!'), t('Multiplier 1x can not be deleted')); + $this->assertNoFieldById('edit-submit'); + + // Add a multiplier. + $new_multiplier = drupal_strtolower($this->randomName()); + $this->drupalGet('admin/config/media/breakpoints/multipliers'); + $edit = array( + 'multipliers[new]' => $new_multiplier, + ); + $this->drupalPost(NULL, $edit, t('Save')); + + // Verify the multiplier was added to the database. + $settings = breakpoints_settings(); + $this->assertTrue(in_array($new_multiplier, $settings->multipliers), t('Multiplier %multiplier was added.', array('%multiplier' => $new_multiplier))); + + // Verify the new multiplier is visible on the multiplier overview page. + $this->assertFieldByName('multipliers[' . $new_multiplier . ']', $new_multiplier); + + // Update a multiplier. + $updated_multiplier = drupal_strtolower($this->randomName()); + $edit = array( + 'multipliers[' . $new_multiplier . ']' => $updated_multiplier, + ); + $this->drupalPost(NULL, $edit, t('Save')); + + // Verify the multiplier was updated in the database. + $settings = breakpoints_settings(); + $this->assertFalse(in_array($new_multiplier, $settings->multipliers), t('Multiplier %multiplier was updated.', array('%multiplier' => $updated_multiplier))); + $this->assertTrue(in_array($updated_multiplier, $settings->multipliers), t('Multiplier %multiplier was updated.', array('%multiplier' => $updated_multiplier))); + + // Verify the updated multiplier is visible on the multiplier overview page. + $this->assertNoFieldByName('multipliers[' . $new_multiplier . ']'); + $this->assertFieldByName('multipliers[' . $updated_multiplier . ']', $updated_multiplier); + $new_multiplier = $updated_multiplier; + + // Verify the default multipliers are visible on the global breakpoints page. + $this->drupalGet('admin/config/media/breakpoints'); + foreach (breakpoints_breakpoint_load_all() as $breakpoint) { + foreach ($settings->multipliers as $multiplier) { + if ($multiplier != '1x') { + $this->assertFieldByName('breakpoints[' . $breakpoint->machine_name . '][multipliers][' . $multiplier . ']'); + } + else { + // Multiplier 1x can not be disabled for any breakpoint. + $this->assertNoFieldByName('breakpoints[' . $breakpoint->machine_name . '][multipliers][' . $multiplier . ']'); + } + } + } + + // Enable a multiplier for a breakpoint and verify if it's enabled on all pages. + $edit = array( + 'breakpoints[breakpoints.theme.breakpoints_test_theme.narrow][multipliers][1.5x]' => 1, + 'breakpoints[breakpoints.theme.breakpoints_test_theme.narrow][multipliers][' . $new_multiplier . ']' => 1, + ); + $this->drupalPost(NULL, $edit, t('Save')); + + // Verify the checkbox for the enabled multipliers is checked on the global breakpoints page. + $this->assertFieldChecked('edit-breakpoints-breakpointsthemebreakpoints-test-themenarrow-multipliers-15x'); + $this->assertFieldChecked('edit-breakpoints-breakpointsthemebreakpoints-test-themenarrow-multipliers-' . drupal_clean_css_identifier($new_multiplier)); + + // Verify the checkbox for the enabled multipliers is checked on the breakpoints page of a group. + $this->drupalGet('admin/config/media/breakpoints/groups/breakpoints_test_theme'); + $this->assertFieldChecked('edit-breakpoints-breakpointsthemebreakpoints-test-themenarrow-multipliers-15x'); + $this->assertFieldChecked('edit-breakpoints-breakpointsthemebreakpoints-test-themenarrow-multipliers-' . drupal_clean_css_identifier($new_multiplier)); + + // Delete a multiplier. + $this->drupalGet('admin/config/media/breakpoints/multipliers/' . $new_multiplier . '/delete'); + $this->drupalPost(NULL, array(), t('Confirm')); + $this->assertText('Multiplier ' . $new_multiplier . ' was deleted'); + + // Verify the deleted multiplier is no longer visible on the multiplier overview page. + $this->drupalGet('admin/config/media/breakpoints/multipliers'); + $this->assertNoFieldByName('multipliers[' . $new_multiplier . ']'); + + // Verify the deleted multiplier is deleted from the database. + $settings = breakpoints_settings(); + $this->assertFalse(in_array($new_multiplier, $settings->multipliers), t('Multiplier %multiplier was deleted.', array('%multiplier' => $new_multiplier))); + + // Verify the deleted multiplier is no longer visible on the breakpoints page. + $this->drupalGet('admin/config/media/breakpoints'); + foreach (breakpoints_breakpoint_load_all() as $breakpoint) { + $this->assertNoFieldByName('breakpoints[' . $breakpoint->machine_name . '][multipliers][' . $new_multiplier . ']'); + } + } +} diff --git a/sites/all/modules/breakpoints/css/breakpoints.admin.css b/sites/all/modules/breakpoints/css/breakpoints.admin.css new file mode 100644 index 0000000000000000000000000000000000000000..1cfe33f33ef0505fa835e539e2ceaeb02b061c2e --- /dev/null +++ b/sites/all/modules/breakpoints/css/breakpoints.admin.css @@ -0,0 +1,11 @@ +tr.odd.breakpoints-status-disabled { + background: none repeat scroll 0 0 #ee7777; +} +tr.even.breakpoints-status-disabled { + background: none repeat scroll 0 0 #ff9999; +} + +a.breakpoints-group-operations-link { + margin-bottom: 1em; + margin-right: 1em; +} diff --git a/sites/all/modules/breakpoints/plugins/export_ui/breakpoints.inc b/sites/all/modules/breakpoints/plugins/export_ui/breakpoints.inc new file mode 100644 index 0000000000000000000000000000000000000000..b5ba0529580899718c2a694a31b6d8bc37778e7a --- /dev/null +++ b/sites/all/modules/breakpoints/plugins/export_ui/breakpoints.inc @@ -0,0 +1,7 @@ +<?php + +/** + * @file + * Breakpoints - Ctools exportables + */ +// @todo: add support for import/export diff --git a/sites/all/modules/breakpoints/tests/breakpoints_theme_test.info b/sites/all/modules/breakpoints/tests/breakpoints_theme_test.info new file mode 100644 index 0000000000000000000000000000000000000000..c496f52fa66eecbafca1285a2a9cca95d84843ca --- /dev/null +++ b/sites/all/modules/breakpoints/tests/breakpoints_theme_test.info @@ -0,0 +1,12 @@ +name = Breakpoints Theme Test +description = Test breakpoints provided by themes +package = Other +core = 7.x +hidden = TRUE + +; Information added by drupal.org packaging script on 2012-11-22 +version = "7.x-1.0" +core = "7.x" +project = "breakpoints" +datestamp = "1353614756" + diff --git a/sites/all/modules/breakpoints/tests/breakpoints_theme_test.module b/sites/all/modules/breakpoints/tests/breakpoints_theme_test.module new file mode 100644 index 0000000000000000000000000000000000000000..34088da441818f423288ced18d0b6e22ba2abf6d --- /dev/null +++ b/sites/all/modules/breakpoints/tests/breakpoints_theme_test.module @@ -0,0 +1,13 @@ +<?php +/** + * @file + * Test breakpoint functionality for breakpoints provided by themes + */ + +/** + * Implements hook_system_theme_info(). + */ +function breakpoints_theme_test_system_theme_info() { + $themes['breakpoints_test_theme'] = drupal_get_path('module', 'breakpoints_theme_test') . '/themes/breakpoints_test_theme/breakpoints_test_theme.info'; + return $themes; +} diff --git a/sites/all/modules/breakpoints/tests/themes/breakpoints_test_theme/breakpoints_test_theme.info b/sites/all/modules/breakpoints/tests/themes/breakpoints_test_theme/breakpoints_test_theme.info new file mode 100644 index 0000000000000000000000000000000000000000..e6c1bfb46ddf417e7e3d1af215616084b14a8ab8 --- /dev/null +++ b/sites/all/modules/breakpoints/tests/themes/breakpoints_test_theme/breakpoints_test_theme.info @@ -0,0 +1,17 @@ +name = Breakpoints test theme +description = Test theme for breakpoints. +core = 7.x +base theme = bartik +hidden = TRUE + +breakpoints[mobile] = (min-width: 0px) +breakpoints[narrow] = (min-width: 560px) +breakpoints[wide] = (min-width: 851px) +breakpoints[tv] = only screen and (min-width: 3456px) + +; Information added by drupal.org packaging script on 2012-11-22 +version = "7.x-1.0" +core = "7.x" +project = "breakpoints" +datestamp = "1353614756" + diff --git a/sites/all/modules/date/CHANGELOG.txt b/sites/all/modules/date/CHANGELOG.txt index 38ecaf947210b5b8c4b6092ffb718f387cfcd90c..99e8fb04613a1042bf525889a0a1ef877e226499 100644 --- a/sites/all/modules/date/CHANGELOG.txt +++ b/sites/all/modules/date/CHANGELOG.txt @@ -5,6 +5,30 @@ Date Module 7.x Version 7.x-2.x-dev =================== +====================== +Version 7.x-2.6 +====================== + +- Issue #1423364 by catmat, Add file and path to hook_context_plugins(). +- Issue #1713248, Remove incorrect use of date_popup() function in previous commit. +- Issue #1143680 by kristiaanvandeneynde, Make Date Popup widget able to accept custom settings. +- Issue #1596546 by applicity_sam, Make sure incorrect month name creates validation error. +- Issue #1432702, Fix some miscellaneous problems with date example dates and formats. +- Issue #1512464 by Liam Moreland, Allow for undefined formatter in Date upgrade handling. +- Issue #1659638 by bojanz, Hide install message if Drupal is not installed (i.e. in install profiles). +- Issue #1605158 by covenantd, Be sure value2 is initiated in Date Context handler. +- Issue #1355256 by barraponto develCuy and KarenS, set default value for date text widget increment to 1, and update existing fields. +- Issue #1561306 by Cyberwolf, Date pager replacement was not working correctly when used with multiple dates. +- Issue #1541740 by hass, Make Date requirements more easily translatable. +- Issue #1543524 by iamEAP, Add update hook to remove the D6 date_timezone field from users. +- Issue #1603640 by bevan, Don't return anything for empty date interval. +- Issue #1235508, Make sure that ISO strings with '00' in the month and/or day don't create dates for the previous month and day. +- Issue #1289270 by pdrake: Fixed date arguments and filters do not work with relationships. +- Issue #895760 by Reinette: Added RFC2447 option 'STATUS' to date_api_ical().inc. +- Issue #606658 by johnmunro and KarenS, Make sure ical import processes multi-day all-day events correctly. +- Issue #1408216 follow up, Need to be sure that NULL is the default state for the sql functions. +- Issue #1540410, Update the Date Tools calendar creation wizard to use the new calendar path. + ====================== Version 7.x-2.5 ====================== diff --git a/sites/all/modules/date/date.field.inc b/sites/all/modules/date/date.field.inc index 5a4fa8134c9282d714f72149b47a3d321665283e..b104702a146acd99b8e4d0d49a8c4ffa12985834 100644 --- a/sites/all/modules/date/date.field.inc +++ b/sites/all/modules/date/date.field.inc @@ -294,12 +294,17 @@ function date_field_widget_info() { 'field types' => array('date', 'datestamp', 'datetime'), ) + $settings, ); + if (module_exists('date_popup')) { $info['date_popup'] = array( 'label' => t('Pop-up calendar'), 'field types' => array('date', 'datestamp', 'datetime'), ) + $settings; } + + // The date text widget should use an increment of 1. + $info['date_text']['increment'] = 1; + return $info; } diff --git a/sites/all/modules/date/date.info b/sites/all/modules/date/date.info index 88eaa6c2ef49f773627a6b863c87376dd83476b2..e5b00c21e51b5ba37f615b2ef82cd28e9359b8f3 100644 --- a/sites/all/modules/date/date.info +++ b/sites/all/modules/date/date.info @@ -10,9 +10,9 @@ files[] = tests/date_field.test files[] = tests/date_validation.test files[] = tests/date_timezone.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date.install b/sites/all/modules/date/date.install index 3f250585c2017ff05de89ff4a67f11633b8c140c..23fb07eb29c1ecb7f5de3208598a674a7a048ebf 100644 --- a/sites/all/modules/date/date.install +++ b/sites/all/modules/date/date.install @@ -160,3 +160,35 @@ function date_update_7003() { module_enable(array('date_repeat_field')); } } + +/** + * Date text widgets should always use an increment of 1. + */ +function date_update_7004() { + + // Select date fields. + $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC)); + $query->join('field_config', 'fc', 'fc.id = fci.field_id'); + $query->fields('fci'); + $query->condition(db_or()->condition('fc.type', 'date')->condition('fc.type', 'datestamp')->condition('fc.type', 'datetime')); + $results = $query->execute(); + + // Find the ones that use the date_text widget. + foreach ($results as $record) { + $instance = unserialize($record['data']); + if (in_array($instance['widget']['type'], array('date_text'))) { + $instance['widget']['settings']['increment'] = 1; + db_update('field_config_instance') + ->fields(array( + 'data' => serialize($instance), + )) + ->condition('field_name', $record['field_name']) + ->condition('entity_type', $record['entity_type']) + ->condition('bundle', $record['bundle']) + ->execute(); + } + } + field_cache_clear(); + drupal_set_message(t('Date text widgets have been updated to use an increment of 1.')); +} + diff --git a/sites/all/modules/date/date.theme b/sites/all/modules/date/date.theme index 0d28be021f238fbffc34d4b6cb7340403d13c0ba..70c6be41f7b87dcc98801330c5de44c8833dc52c 100644 --- a/sites/all/modules/date/date.theme +++ b/sites/all/modules/date/date.theme @@ -301,7 +301,13 @@ function theme_date_display_interval($variables) { 'interval' => $options['interval'], 'interval_display' => $options['interval_display'], ); - return '<span class="date-display-interval"' . drupal_attributes($attributes) . '>' . theme('date_time_ago', $time_ago_vars) . '</span>'; + + if ($return = theme('date_time_ago', $time_ago_vars)) { + return '<span class="date-display-interval"' . drupal_attributes($attributes) . ">$return</span>"; + } + else { + return ''; + } } /** diff --git a/sites/all/modules/date/date_admin.inc b/sites/all/modules/date/date_admin.inc index f5a345b4e1bfb5f5f05e9e364fb858eb3bf38349..993aa091151606d1fe20a997fef38b568e0ea20a 100644 --- a/sites/all/modules/date/date_admin.inc +++ b/sites/all/modules/date/date_admin.inc @@ -122,6 +122,7 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) $formatter = $display['type']; $format_types = date_format_type_options(); $summary = array(); + $format = FALSE; switch ($formatter) { case 'date_plain': $format = t('Plain'); @@ -130,9 +131,16 @@ function date_default_formatter_settings_summary($field, $instance, $view_mode) $format = t('Interval'); break; default: - $format = $format_types[$settings['format_type']]; + if (!empty($format_types[$settings['format_type']])) { + $format = $format_types[$settings['format_type']]; + } + } + if ($format) { + $summary[] = t('Display dates using the @format format', array('@format' => $format)); + } + else { + $summary[] = t('Display dates using the default format because the specified format (@format) is not defined', array('@format' => $settings['format_type'])); } - $summary[] = t('Display dates using the @format format', array('@format' => $format)); if (array_key_exists('fromto', $settings) && $field['settings']['todate']) { $options = array( diff --git a/sites/all/modules/date/date_all_day/date_all_day.info b/sites/all/modules/date/date_all_day/date_all_day.info index ba5d0f185e26f1f8b8a7177db83c447a2be6ad06..8f8bf48b72019bcbe73b831cdd4900629f4a431d 100644 --- a/sites/all/modules/date/date_all_day/date_all_day.info +++ b/sites/all/modules/date/date_all_day/date_all_day.info @@ -5,9 +5,9 @@ dependencies[] = date package = Date/Time core = 7.x -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_api/date_api.info b/sites/all/modules/date/date_api/date_api.info index 09c0983ba95656b9ecbe8dd35240987f2e393db0..ee1a38423bf2c5de0b06785ee9974dced551fac7 100644 --- a/sites/all/modules/date/date_api/date_api.info +++ b/sites/all/modules/date/date_api/date_api.info @@ -9,9 +9,9 @@ stylesheets[all][] = date.css files[] = date_api.module files[] = date_api_sql.inc -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_api/date_api.install b/sites/all/modules/date/date_api/date_api.install index d2bfbb36794f2c1d00565a4a017f68a24ea51c20..ce5b746dd77b95e7c84cdf37ec22708afaf7a614 100644 --- a/sites/all/modules/date/date_api/date_api.install +++ b/sites/all/modules/date/date_api/date_api.install @@ -61,17 +61,20 @@ function date_api_requirements($phase) { * Implements hook_install(). */ function date_api_install() { - // Ensure translations don't break at install time. - $t = get_t(); + // Only set the message if Drupal itself is already installed. + if (variable_get('install_task') == 'done') { + // Ensure translations don't break at install time. + $t = get_t(); - // date_api_set_variables can install date_timezone. The - // date_timezone_install() function does a module_enable('date_api'). This - // means that date_api_enable() can be called before date_api_install() - // finishes! So the date_api schema needs to be installed before this line! - date_api_set_variables(); + // date_api_set_variables can install date_timezone. The + // date_timezone_install() function does a module_enable('date_api'). This + // means that date_api_enable() can be called before date_api_install() + // finishes! So the date_api schema needs to be installed before this line! + date_api_set_variables(); - $message = $t('The Date API requires that you set up the !timezone_link and the !format_link to function correctly.', array('!timezone_link' => l($t('site timezone and first day of week settings'), 'admin/config/regional/settings'), '!format_link' => l($t('date format settings'), 'admin/config/regional/date-time'))); - drupal_set_message(filter_xss_admin($message), 'warning'); + $message = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone and first day of week settings</a> and the <a href="@regional_date_time">date format settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'), '@regional_date_time' => url('admin/config/regional/date-time'))); + drupal_set_message(filter_xss_admin($message), 'warning'); + } } /** @@ -237,3 +240,13 @@ function date_api_update_7000() { db_drop_table('d6_date_format_locale'); } } + + +/** + * Drop D6 timezone_name field on {users} after upgrading to D7. + */ +function date_api_update_7001() { + if (db_field_exists('users', 'timezone_name')) { + db_drop_field('users', 'timezone_name'); + } +} diff --git a/sites/all/modules/date/date_api/date_api.module b/sites/all/modules/date/date_api/date_api.module index b8c95f775fb68ed1d58dde4a191e73b886ce8c9d..d85a8a5e5a132400f59f18373ec1d4a47f9d413c 100644 --- a/sites/all/modules/date/date_api/date_api.module +++ b/sites/all/modules/date/date_api/date_api.module @@ -83,28 +83,28 @@ function date_api_status() { $value = variable_get('date_default_timezone'); if (isset($value)) { - $success_messages[] = $t('The timezone has been set to !value_link.', array('!value_link' => l($value, 'admin/config/regional/settings'))); + $success_messages[] = $t('The timezone has been set to <a href="@regional_settings">@timezone</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@timezone' => $value)); } else { - $error_messages[] = $t('The Date API requires that you set up the !timezone_link to function correctly.', array('!timezone_link' => l($t('site timezone'), 'admin/config/regional/settings'))); + $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'))); } $value = variable_get('date_first_day'); if (isset($value)) { $days = date_week_days(); - $success_messages[] = $t('The first day of the week has been set to !value_link.', array('!value_link' => l($days[$value], 'admin/config/regional/settings'))); + $success_messages[] = $t('The first day of the week has been set to <a href="@regional_settings">@day</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@day' => $days[$value])); } else { - $error_messages[] = $t('The Date API requires that you set up the !first_day_link to function correctly.', array('!first_day_link' => l($t('site first day of week settings'), 'admin/config/regional/settings'))); + $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site first day of week settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'))); } $value = variable_get('date_format_medium'); if (isset($value)) { $now = date_now(); - $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at !value_link.', array('@value' => $now->format($value), '!value_link' => l(t('Date and time'), 'admin/config/regional/date-time'))); + $success_messages[] = $t('The medium date format type has been set to to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at <a href="@regional_date_time">Date and time</a> settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time'))); } else { - $error_messages[] = $t('The Date API requires that you set up the !format_link to function correctly.', array('!format_link' => l($t('system date formats'), 'admin/config/regional/date-time'))); + $error_messages[] = $t('The Date API requires that you set up the <a href="@regional_date_time">system date formats</a> to function correctly.', array('@regional_date_time' => url('admin/config/regional/date-time'))); } return array('errors', $error_messages, 'success' => $success_messages); @@ -185,7 +185,7 @@ class DateObject extends DateTime { * Constructs a date object. * * @param string $time - * A date/time string. Defaults to 'now'. + * A date/time string or array. Defaults to 'now'. * @param object|string|null $tz * PHP DateTimeZone object, string or NULL allowed. Defaults to NULL. * @param string $format @@ -198,6 +198,9 @@ class DateObject extends DateTime { $this->timeOnly = FALSE; $this->dateOnly = FALSE; + // Store the raw time input so it is available for validation. + $this->originalTime = $time; + // Allow string timezones. if (!empty($tz) && !is_object($tz)) { $tz = new DateTimeZone($tz); @@ -221,8 +224,7 @@ class DateObject extends DateTime { $format = DATE_FORMAT_DATETIME; $this->addGranularity('timezone'); } - - if (is_array($time)) { + elseif (is_array($time)) { // Assume we were passed an indexed array. if (empty($time['year']) && empty($time['month']) && empty($time['day'])) { $this->timeOnly = TRUE; @@ -237,6 +239,12 @@ class DateObject extends DateTime { // We checked for errors already, skip parsing the input values. $format = NULL; } + else { + // Make sure dates like 2010-00-00T00:00:00 get converted to + // 2010-01-01T00:00:00 before creating a date object + // to avoid unintended changes in the month or day. + $time = date_make_iso_valid($time); + } // The parse function will also set errors on the date parts. if (!empty($format)) { @@ -590,12 +598,12 @@ class DateObject extends DateTime { break; case 'F': $array_month_long = array_flip(date_month_names()); - $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : ''; + $final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1; $this->addGranularity('month'); break; case 'M': $array_month = array_flip(date_month_names_abbr()); - $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : ''; + $final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1; $this->addGranularity('month'); break; case 'Y': @@ -2667,3 +2675,33 @@ function date_is_date($date) { } return TRUE; } + +/** + * This function will replace ISO values that have the pattern 9999-00-00T00:00:00 + * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO + * dates and ensure that date objects created from this value contain a valid month + * and day. Without this fix, the ISO date '2020-00-00T00:00:00' would be created as + * November 30, 2019 (the previous day in the previous month). + * + * @param string $iso_string + * An ISO string that needs to be made into a complete, valid date. + * + * @TODO Expand on this to work with all sorts of partial ISO dates. + */ +function date_make_iso_valid($iso_string) { + // If this isn't a value that uses an ISO pattern, there is nothing to do. + if (is_numeric($iso_string) || !preg_match(DATE_REGEX_ISO, $iso_string)) { + return $iso_string; + } + // First see if month and day parts are '-00-00'. + if (substr($iso_string, 4, 6) == '-00-00') { + return preg_replace('/([\d]{4}-)(00-00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01-01${3}', $iso_string); + } + // Then see if the day part is '-00'. + elseif (substr($iso_string, 7, 3) == '-00') { + return preg_replace('/([\d]{4}-[\d]{2}-)(00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01${3}', $iso_string); + } + + // Fall through, no changes required. + return $iso_string; +} diff --git a/sites/all/modules/date/date_api/date_api_elements.inc b/sites/all/modules/date/date_api/date_api_elements.inc index b5a90a0e8a9e37c8e39b637b6c5c1446a86aac8a..7da4e5892c79dbc4c9997f202ee69e161b1be6cf 100644 --- a/sites/all/modules/date/date_api/date_api_elements.inc +++ b/sites/all/modules/date/date_api/date_api_elements.inc @@ -294,7 +294,7 @@ function date_text_element_value_callback($element, $input = FALSE, &$form_state $date = date_default_date($element); } if (date_is_date($date)) { - $return['date'] = $date->format($element['#date_format']); + $return['date'] = date_format_date($date, 'custom', $element['#date_format']); } return $return; } @@ -320,7 +320,7 @@ function date_text_element_process($element, &$form_state, $form) { $element['date']['#weight'] = !empty($element['date']['#weight']) ? $element['date']['#weight'] : $element['#weight']; $element['date']['#attributes'] = array('class' => isset($element['#attributes']['class']) ? $element['#attributes']['class'] += array('date-date') : array('date-date')); $now = date_example_date(); - $element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_now()->format($element['#date_format']))); + $element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_format_date(date_example_date(), 'custom', $element['#date_format']))); $element['date']['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE; // Keep the system from creating an error message for the sub-element. diff --git a/sites/all/modules/date/date_api/date_api_ical.inc b/sites/all/modules/date/date_api/date_api_ical.inc index 7cb2b957745d9d7ed22459e15c4e7d84484dc0f5..2ca484e7fdc320b525ad9fc1b7cdc5c28daba4d5 100644 --- a/sites/all/modules/date/date_api/date_api_ical.inc +++ b/sites/all/modules/date/date_api/date_api_ical.inc @@ -215,15 +215,31 @@ function date_ical_parse($icaldatafolded = array()) { // even when you are working with only a portion of the VEVENT // array, like in Feed API parsers. $subgroup['all_day'] = FALSE; - if (!empty($subgroup['DTSTART']) && (!empty($subgroup['DTSTART']['all_day']) || - (empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT'])))) { - // Many programs output DTEND for an all day event as the - // following day, reset this to the same day for internal use. - $subgroup['all_day'] = TRUE; + + // iCal spec states 'The "DTEND" property for a "VEVENT" calendar + // component specifies the non-inclusive end of the event'. Adjust + // multi-day events to remove the extra day because the Date code + // assumes the end date is inclusive. + if (!empty($subgroup['DTEND']) && (!empty($subgroup['DTEND']['all_day']))) { + // Make the end date one day earlier. + $date = new DateObject ($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']); + date_modify($date, '-1 day'); + $subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d'); + } + // If a start datetime is defined AND there is no definition for + // the end datetime THEN make the end datetime equal the start + // datetime and if it is an all day event define the entire event + // as a single all day event. + if (!empty($subgroup['DTSTART']) && + (empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT']))) { $subgroup['DTEND'] = $subgroup['DTSTART']; } // Add this element to the parent as an array under the component // name. + if (!empty($subgroup['DTSTART']['all_day'])) { + $subgroup['all_day'] = TRUE; + } + // Add this element to the parent as an array under the prev($subgroups); $parent = &$subgroups[key($subgroups)]; @@ -291,6 +307,7 @@ function date_ical_parse($icaldatafolded = array()) { $parse_result = date_ical_parse_rrule($field, $data); break; + case 'STATUS': case 'SUMMARY': case 'DESCRIPTION': $parse_result = date_ical_parse_text($field, $data); @@ -712,9 +729,12 @@ function date_api_ical_build_rrule($form_values) { // We only collect a date for UNTIL, but we need it to be inclusive, so // force it to a full datetime element at the last second of the day. if (!is_object($form_values['UNTIL']['datetime'])) { - $form_values['UNTIL']['datetime'] .= ' 23:59:59'; - $form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); - $form_values['UNTIL']['all_day'] = 0; + // If this is a date without time, give it time. + if (strlen($form_values['UNTIL']['datetime']) < 11) { + $form_values['UNTIL']['datetime'] .= ' 23:59:59'; + $form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second'))); + $form_values['UNTIL']['all_day'] = FALSE; + } $until = date_ical_date($form_values['UNTIL'], 'UTC'); } else { @@ -745,7 +765,7 @@ function date_api_ical_build_rrule($form_values) { foreach ($form_values['EXDATE'] as $value) { if (!empty($value['datetime'])) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? $date->format(DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } @@ -765,7 +785,7 @@ function date_api_ical_build_rrule($form_values) { $ex_dates = array(); foreach ($form_values['RDATE'] as $value) { $date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime']; - $ex_date = !empty($date) ? $date->format(DATE_FORMAT_ICAL) . 'Z': ''; + $ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': ''; if (!empty($ex_date)) { $ex_dates[] = $ex_date; } diff --git a/sites/all/modules/date/date_api/date_api_sql.inc b/sites/all/modules/date/date_api/date_api_sql.inc index ede8b1ef33fbd1071b828de1c863078693b300d9..a2adc652b414c3f72ae3e8b91af3735b6aeab9af 100644 --- a/sites/all/modules/date/date_api/date_api_sql.inc +++ b/sites/all/modules/date/date_api/date_api_sql.inc @@ -598,7 +598,7 @@ class date_sql_handler { * @return string * SQL for the where clause for this operation. */ - function sql_where_date($type, $field, $operator, $value, $adjustment = 0) { + function sql_where_date($type, $field, $operator, $value, $adjustment = NULL) { $type = strtoupper($type); if (strtoupper($value) == 'NOW') { $value = $this->sql_field('NOW', $adjustment); @@ -644,12 +644,12 @@ class date_sql_handler { * @return string * SQL for the where clause for this operation. */ - function sql_where_extract($part, $field, $operator, $value) { - if ($this->local_timezone != $this->db_timezone) { + function sql_where_extract($part, $field, $operator, $value, $adjustment = NULL) { + if (empty($adjustment) && $this->local_timezone != $this->db_timezone) { $field = $this->sql_field($field); } else { - $field = $this->sql_field($field, 0); + $field = $this->sql_field($field, $adjustment); } return $this->sql_extract($part, $field) . " $operator $value"; } @@ -670,12 +670,12 @@ class date_sql_handler { * @return string * SQL for the where clause for this operation. */ - function sql_where_format($format, $field, $operator, $value) { - if ($this->local_timezone != $this->db_timezone) { + function sql_where_format($format, $field, $operator, $value, $adjustment = NULL) { + if (empty($adjustment) && $this->local_timezone != $this->db_timezone) { $field = $this->sql_field($field); } else { - $field = $this->sql_field($field, 0); + $field = $this->sql_field($field, $adjustment); } return $this->sql_format($format, $field) . " $operator '$value'"; } diff --git a/sites/all/modules/date/date_context/date_context.info b/sites/all/modules/date/date_context/date_context.info index 63f012901831d930e3b9a4f374a3673df64143c4..a69a599b74ee048a9babe66521e4772a57e6b071 100644 --- a/sites/all/modules/date/date_context/date_context.info +++ b/sites/all/modules/date/date_context/date_context.info @@ -8,9 +8,9 @@ dependencies[] = context files[] = date_context.module files[] = plugins/date_context_date_condition.inc -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_context/date_context.module b/sites/all/modules/date/date_context/date_context.module index a5d265084083123a4a40fe63b5f55121f3a84037..44e975acd84ecbe39175584bcdc007a7d3a46591 100644 --- a/sites/all/modules/date/date_context/date_context.module +++ b/sites/all/modules/date/date_context/date_context.module @@ -30,6 +30,8 @@ function date_context_context_plugins() { 'handler' => array( 'class' => 'date_context_date_condition', 'parent' => 'context_condition_node', + 'path' => drupal_get_path('module', 'date_context') . '/plugins', + 'file' => 'date_context_date_condition.inc', ), ); return $plugins; diff --git a/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc b/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc index 3fcb042857105e04007ad75afb0ff4b1a3dce4e6..733fb5a84709a4e6aaeae24f7c390406f3d7f3e3 100644 --- a/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc +++ b/sites/all/modules/date/date_context/plugins/date_context_date_condition.inc @@ -82,6 +82,9 @@ class date_context_date_condition extends context_condition_node { $date = new DateObject($item['value'], $timezone_db); date_timezone_set($date, timezone_open($timezone)); $date1 = $date->format(DATE_FORMAT_DATETIME); + if (empty($item['value2'])) { + $item['value2'] = $item['value']; + } $date = new DateObject($item['value2'], $timezone_db); date_timezone_set($date, timezone_open($timezone)); $date2 = $date->format(DATE_FORMAT_DATETIME); diff --git a/sites/all/modules/date/date_migrate/date_migrate.info b/sites/all/modules/date/date_migrate/date_migrate.info index 487fec8446743635a0395b76d7e667b6d545a47d..c0d536f5b7090acc08aacce717f48f00bd702348 100644 --- a/sites/all/modules/date/date_migrate/date_migrate.info +++ b/sites/all/modules/date/date_migrate/date_migrate.info @@ -8,9 +8,9 @@ dependencies[] = date files[] = date.migrate.inc files[] = date_migrate.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info b/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info index ad43d983fa49cd94ecf8f245e24e2b002bb2f496..b54a3f7515d045c4dd048224a7cf34d81202e77a 100644 --- a/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info +++ b/sites/all/modules/date/date_migrate/date_migrate_example/date_migrate_example.info @@ -21,9 +21,9 @@ package = "Features" project = "date_migrate_example" version = "7.x-2.0" -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_popup/date_popup.info b/sites/all/modules/date/date_popup/date_popup.info index f781721292ad9385fb9cee69af3564fc59c8fcb5..767aacac22470bc8ae08115ea6841a5700c4cf6c 100644 --- a/sites/all/modules/date/date_popup/date_popup.info +++ b/sites/all/modules/date/date_popup/date_popup.info @@ -7,9 +7,9 @@ configure = admin/config/date/date_popup stylesheets[all][] = themes/datepicker.1.7.css -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_popup/date_popup.module b/sites/all/modules/date/date_popup/date_popup.module index aa780333361d3ebf464df26e55a0e2decbc704d7..ca292ef9ad75c83fe9d90422fa2d36260c537c8f 100644 --- a/sites/all/modules/date/date_popup/date_popup.module +++ b/sites/all/modules/date/date_popup/date_popup.module @@ -185,6 +185,15 @@ function date_popup_theme() { * The number of years to go back and forward in a year selector, * default is -3:+3 (3 back and 3 forward). * + * #datepicker_options + * An associative array representing the jQuery datepicker options you want + * to set for this element. Use the jQuery datepicker option names as keys. + * Hard coded defaults are: + * - changeMonth => TRUE + * - changeYear => TRUE + * - autoPopUp => 'focus' + * - closeAtTop => FALSE + * - speed => 'immediate' */ function date_popup_element_info() { $timepicker = date_popup_get_preferred_timepicker(); @@ -194,6 +203,7 @@ function date_popup_element_info() { '#date_timezone' => date_default_timezone(), '#date_flexible' => 0, '#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'), + '#datepicker_options' => array(), '#timepicker' => variable_get('date_popup_timepicker', $timepicker), '#date_increment' => 1, '#date_year_range' => '-3:+3', @@ -317,20 +327,22 @@ function date_popup_process_date_part(&$element) { $range = date_range_years($element['#date_year_range'], $date); $year_range = date_range_string($range); - $settings = array( + // Add the dynamic datepicker options. Allow element-specific datepicker + // preferences to override these options for whatever reason they see fit. + $settings = $element['#datepicker_options'] + array( 'changeMonth' => TRUE, 'changeYear' => TRUE, - 'firstDay' => intval(variable_get('date_first_day', 0)), - //'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png", - //'buttonImageOnly' => TRUE, 'autoPopUp' => 'focus', 'closeAtTop' => FALSE, 'speed' => 'immediate', + 'firstDay' => intval(variable_get('date_first_day', 0)), + //'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png", + //'buttonImageOnly' => TRUE, 'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'), 'yearRange' => $year_range, // Custom setting, will be expanded in Drupal.behaviors.date_popup() 'fromTo' => isset($fromto), - ); + ); // Create a unique id for each set of custom settings. $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings); @@ -353,7 +365,7 @@ function date_popup_process_date_part(&$element) { ); $sub_element['#value'] = $sub_element['#default_value']; // TODO, figure out exactly when we want this description. In many places it is not desired. - $sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_now(), 'custom', date_popup_date_format($element)))); + $sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_example_date(), 'custom', date_popup_date_format($element)))); return $sub_element; } diff --git a/sites/all/modules/date/date_repeat/date_repeat.info b/sites/all/modules/date/date_repeat/date_repeat.info index c1ff662e19769a6c144bdf220097953430ca633f..1d1720a47f82467b75afda26f91a26000d0f8aca 100644 --- a/sites/all/modules/date/date_repeat/date_repeat.info +++ b/sites/all/modules/date/date_repeat/date_repeat.info @@ -7,9 +7,9 @@ php = 5.2 files[] = tests/date_repeat.test files[] = tests/date_repeat_form.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_repeat_field/date_repeat_field.info b/sites/all/modules/date/date_repeat_field/date_repeat_field.info index 0b57d62d74d320f302bdb706fa41e02a02836397..803105c24ccc8a2d49f53cd04ff4f7da0c59a296 100644 --- a/sites/all/modules/date/date_repeat_field/date_repeat_field.info +++ b/sites/all/modules/date/date_repeat_field/date_repeat_field.info @@ -7,9 +7,9 @@ stylesheets[all][] = date_repeat_field.css package = Date/Time core = 7.x -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_tools/date_tools.info b/sites/all/modules/date/date_tools/date_tools.info index a34dcc8339b8eb035e768d0af587a0c4ee14ae99..3f392a4257d310a1fcc0e6d2a25a5dba1b247bd2 100644 --- a/sites/all/modules/date/date_tools/date_tools.info +++ b/sites/all/modules/date/date_tools/date_tools.info @@ -6,9 +6,9 @@ core = 7.x configure = admin/config/date/tools files[] = tests/date_tools.test -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_tools/date_tools.wizard.inc b/sites/all/modules/date/date_tools/date_tools.wizard.inc index 034cf3756494507dbdadc360dd4624ec2eade829..14bc2759ead0ffe598303ff3a0690ca7f8bcf849 100644 --- a/sites/all/modules/date/date_tools/date_tools.wizard.inc +++ b/sites/all/modules/date/date_tools/date_tools.wizard.inc @@ -264,7 +264,7 @@ function date_tools_wizard_build($form_values) { $field = field_create_field($field); $instance = field_create_instance($instance); - $view_name = 'calendar_' . $field_name; + $view_name = 'calendar_node_' . $field_name; field_info_cache_clear(TRUE); field_cache_clear(TRUE); diff --git a/sites/all/modules/date/date_views/date_views.info b/sites/all/modules/date/date_views/date_views.info index 9216f8e01a38d8ea2f3e18e4415ef42e84134576..ef7da6c0c137f27ea2a3d19c93c5b828f6efbc19 100644 --- a/sites/all/modules/date/date_views/date_views.info +++ b/sites/all/modules/date/date_views/date_views.info @@ -13,9 +13,9 @@ files[] = includes/date_views.views_default.inc files[] = includes/date_views.views.inc files[] = includes/date_views_plugin_pager.inc -; Information added by drupal.org packaging script on 2012-04-19 -version = "7.x-2.5" +; Information added by drupal.org packaging script on 2012-08-13 +version = "7.x-2.6" core = "7.x" project = "date" -datestamp = "1334835098" +datestamp = "1344850024" diff --git a/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc b/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc index bba39a6be8380d817004ede7eb15fac684e861b4..61aeafc1802b1bcf50bc25c8f8206b1bf1fe7bd8 100644 --- a/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc +++ b/sites/all/modules/date/date_views/includes/date_views_argument_handler.inc @@ -150,6 +150,9 @@ class date_views_argument_handler extends date_views_argument_handler_simple { $this->query->set_where_group($this->options['date_method'], $this->options['date_group']); $this->granularity = $this->date_handler->arg_granularity($this->argument); $format = $this->date_handler->views_formats($this->granularity, 'sql'); + + $this->placeholders = array(); + if (!empty($this->query_fields)) { // Use set_where_group() with the selected date_method // of 'AND' or 'OR' to create the where clause. @@ -161,7 +164,7 @@ class date_views_argument_handler extends date_views_argument_handler_simple { $this->table = $field['table_name']; $this->original_table = $field['table_name']; if ($field['table_name'] != $this->table || !empty($this->relationship)) { - $this->table = $this->query->queue_table($field['table_name'], $this->relationship); + $this->table = $this->query->ensure_table($field['table_name'], $this->relationship); } // $this->table_alias gets set when the first field is processed if otherwise empty. // For subsequent fields, we need to be sure it is emptied again. @@ -169,6 +172,8 @@ class date_views_argument_handler extends date_views_argument_handler_simple { $this->table_alias = NULL; } parent::query($group_by); + + $this->placeholders = array_merge($this->placeholders, $this->date_handler->placeholders); } } } diff --git a/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc b/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc index 63467a2615b1cce2148b00394de3eb64d7b194b3..5eb5ebc90242a59c86acb7bbadbf43279949b7e4 100644 --- a/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc +++ b/sites/all/modules/date/date_views/includes/date_views_filter_handler.inc @@ -58,7 +58,7 @@ class date_views_filter_handler extends date_views_filter_handler_simple { // Respect relationships when determining the table alias. if ($field['table_name'] != $this->table || !empty($this->relationship)) { - $this->related_table_alias = $this->query->queue_table($field['table_name'], $this->relationship); + $this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship); } $table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name']; $field_name = $table_alias . '.' . $field['field_name']; @@ -174,4 +174,4 @@ class date_views_filter_handler extends date_views_filter_handler_simple { } } } -} \ No newline at end of file +} diff --git a/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc b/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc index 10c6581be39c74f5e4c9b6f05768b3dc1b1b3e5f..f9594a72f208e001a8cdadf64b89d5d5fee58b71 100644 --- a/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc +++ b/sites/all/modules/date/date_views/includes/date_views_plugin_pager.inc @@ -168,7 +168,7 @@ class date_views_plugin_pager extends views_plugin_pager { if (empty($this->view->date_info)) $this->view->date_info = new stdClass(); $this->view->date_info->granularity = $argument->date_handler->granularity; $format = $this->view->date_info->granularity == 'week' ? DATE_FORMAT_DATETIME : $argument->sql_format; - $this->view->date_info->placeholders = $argument->date_handler->placeholders; + $this->view->date_info->placeholders = isset($argument->placeholders) ? $argument->placeholders : $argument->date_handler->placeholders; $this->view->date_info->date_arg = $argument->argument; $this->view->date_info->date_arg_pos = $i; $this->view->date_info->year = date_format($argument->min_date, 'Y'); diff --git a/sites/all/modules/date/tests/date_api.test b/sites/all/modules/date/tests/date_api.test index aa39ac319abf32581eda851554b295f6e075df39..f50020c15db78d7b356360e1b47f466785cc29a3 100644 --- a/sites/all/modules/date/tests/date_api.test +++ b/sites/all/modules/date/tests/date_api.test @@ -388,6 +388,14 @@ class DateAPITestCase extends DrupalWebTestCase { foreach ($invalid as $range) { $this->assertFalse(date_range_valid($range), "$range recognized as an invalid date range."); } + + // Test for invalid month names when we are using a short version of the month + $input = '23 abc 2012'; + $timezone = NULL; + $format = 'd M Y'; + $date = new dateObject($input, $timezone, $format); + $this->assertNotEqual(count($date->errors), 0, '23 abc 2012 should be an invalid date'); + } /** diff --git a/sites/all/modules/diff b/sites/all/modules/diff new file mode 160000 index 0000000000000000000000000000000000000000..adb43040a4e8f950ae604b7d3698f493075cb04f --- /dev/null +++ b/sites/all/modules/diff @@ -0,0 +1 @@ +Subproject commit adb43040a4e8f950ae604b7d3698f493075cb04f diff --git a/sites/all/modules/field_collection/LICENSE.txt b/sites/all/modules/field_collection/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/field_collection/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/field_collection/README.txt b/sites/all/modules/field_collection/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..6ae4ab265ced1ce4d8500360833ee8fbfcda7928 --- /dev/null +++ b/sites/all/modules/field_collection/README.txt @@ -0,0 +1,36 @@ + +Field collection +----------------- +Provides a field collection field, to which any number of fields can be attached. + +Each field collection item is internally represented as an entity, which is +referenced via the field collection field in the host entity. While +conceptually field collections are treated as part of the host entity, each +field collection item may also be viewed and edited separately. + + + Usage + ------ + + * Add a field collection field to any entity, e.g. to a node. For that use the + the usual "Manage fields" interface provided by the "field ui" module of + Drupal, e.g. "Admin -> Structure-> Content types -> Article -> Manage fields". + + * Then go to "Admin -> Structure-> Field collection" to define some fields for + the created field collection. + + * By the default, the field collection is not shown during editing of the host + entity. However, some links for adding, editing or deleting field collection + items is shown when the host entity is viewed. + + * Widgets for embedding the form for creating field collections in the + host-entity can be provided by any module. In future the field collection + module might provide such widgets itself too. + + +Restrictions +------------- + + * As of now, the field collection field does not properly respect different + languages of the host entity. Thus, for now it is suggested to only use the + field for entities that are not translatable. \ No newline at end of file diff --git a/sites/all/modules/field_collection/field-collection-item.tpl.php b/sites/all/modules/field_collection/field-collection-item.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..8d345cb6c55221dcf3cddff3eae47346880a383c --- /dev/null +++ b/sites/all/modules/field_collection/field-collection-item.tpl.php @@ -0,0 +1,37 @@ +<?php + +/** + * @file + * Default theme implementation for field collection items. + * + * Available variables: + * - $content: An array of comment items. Use render($content) to print them all, or + * print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $title: The (sanitized) field collection item label. + * - $url: Direct url of the current entity if specified. + * - $page: Flag for the full page state. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. By default the following classes are available, where + * the parts enclosed by {} are replaced by the appropriate values: + * - entity-field-collection-item + * - field-collection-item-{field_name} + * + * Other variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess() + * @see template_preprocess_entity() + * @see template_process() + */ +?> +<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + <div class="content"<?php print $content_attributes; ?>> + <?php + print render($content); + ?> + </div> +</div> diff --git a/sites/all/modules/field_collection/field_collection.admin.inc b/sites/all/modules/field_collection/field_collection.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..b6328a44ff4ed9e537fed0c03054448bb6f2a921 --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.admin.inc @@ -0,0 +1,47 @@ +<?php + +/** + * @file + * Provides the field_collection module admin pages. + */ + +/** + * Menu callback; list all field collections on this site. + */ +function field_collections_overview() { + $instances = field_info_instances(); + $field_types = field_info_field_types(); + $bundles = field_info_bundles(); + $header = array(t('Field name'), t('Used in'), array('data' => t('Operations'), 'colspan' => '2')); + $rows = array(); + foreach ($instances as $entity_type => $type_bundles) { + foreach ($type_bundles as $bundle => $bundle_instances) { + foreach ($bundle_instances as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['type'] == 'field_collection') { + $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); + $rows[$field_name]['class'] = $field['locked'] ? array('menu-disabled') : array(''); + + $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + $rows[$field_name]['data'][1][] = l($bundles[$entity_type][$bundle]['label'], $admin_path . '/fields'); + } + } + } + } + foreach ($rows as $field_name => $cell) { + $rows[$field_name]['data'][1] = implode(', ', $cell['data'][1]); + + $field_name_url_str = strtr($field_name, array('_' => '-')); + $rows[$field_name]['data'][2] = l(t('manage fields'), 'admin/structure/field-collections/' . $field_name_url_str . '/fields'); + $rows[$field_name]['data'][3] = l(t('manage display'), 'admin/structure/field-collections/' . $field_name_url_str . '/display'); + } + if (empty($rows)) { + $output = t('No field collections have been defined yet. To do so attach a field collection field to any entity.'); + } + else { + // Sort rows by field name. + ksort($rows); + $output = theme('table', array('header' => $header, 'rows' => $rows)); + } + return $output; +} diff --git a/sites/all/modules/field_collection/field_collection.api.php b/sites/all/modules/field_collection/field_collection.api.php new file mode 100644 index 0000000000000000000000000000000000000000..28e827376da860286c51e994f8b35966a3464f34 --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.api.php @@ -0,0 +1,169 @@ +<?php + +/** + * @file + * Contains API documentation and examples for the Field collection module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Alter whether a field collection item is considered empty. + * + * This hook allows modules to determine whether a field collection is empty + * before it is saved. + * + * @param boolean $empty + * Whether or not the field should be considered empty. + * @param FieldCollectionItemEntity $item + * The field collection we are currently operating on. + */ +function hook_field_collection_is_empty_alter(&$is_empty, FieldCollectionItemEntity $item) { + if (isset($item->my_field) && empty($item->my_field)) { + $is_empty = TRUE; + } +} + +/** + * Acts on field collections being loaded from the database. + * + * This hook is invoked during field collection item loading, which is handled + * by entity_load(), via the EntityCRUDController. + * + * @param array $entities + * An array of field collection item entities being loaded, keyed by id. + * + * @see hook_entity_load() + */ +function hook_field_collection_item_load(array $entities) { + $result = db_query('SELECT pid, foo FROM {mytable} WHERE pid IN(:ids)', array(':ids' => array_keys($entities))); + foreach ($result as $record) { + $entities[$record->pid]->foo = $record->foo; + } +} + +/** + * Responds when a field collection item is inserted. + * + * This hook is invoked after the field collection item is inserted into the + * database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being inserted. + * + * @see hook_entity_insert() + */ +function hook_field_collection_item_insert(FieldCollectionItemEntity $field_collection_item) { + db_insert('mytable')->fields(array( + 'id' => entity_id('field_collection_item', $field_collection_item), + 'extra' => print_r($field_collection_item, TRUE), + ))->execute(); +} + +/** + * Acts on a field collection item being inserted or updated. + * + * This hook is invoked before the field collection item is saved to the database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being inserted or updated. + * + * @see hook_entity_presave() + */ +function hook_field_collection_item_presave(FieldCollectionItemEntity $field_collection_item) { + $field_collection_item->name = 'foo'; +} + +/** + * Responds to a field collection item being updated. + * + * This hook is invoked after the field collection item has been updated in the + * database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being updated. + * + * @see hook_entity_update() + */ +function hook_field_collection_item_update(FieldCollectionItemEntity $field_collection_item) { + db_update('mytable') + ->fields(array('extra' => print_r($field_collection_item, TRUE))) + ->condition('id', entity_id('field_collection_item', $field_collection_item)) + ->execute(); +} + +/** + * Responds to field collection item deletion. + * + * This hook is invoked after the field collection item has been removed from + * the database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being deleted. + * + * @see hook_entity_delete() + */ +function hook_field_collection_item_delete(FieldCollectionItemEntity $field_collection_item) { + db_delete('mytable') + ->condition('pid', entity_id('field_collection_item', $field_collection_item)) + ->execute(); +} + +/** + * Act on a field collection item that is being assembled before rendering. + * + * @param $field_collection_item + * The field collection item entity. + * @param $view_mode + * The view mode the field collection item is rendered in. + * @param $langcode + * The language code used for rendering. + * + * The module may add elements to $field_collection_item->content prior to + * rendering. The structure of $field_collection_item->content is a renderable + * array as expected by drupal_render(). + * + * @see hook_entity_prepare_view() + * @see hook_entity_view() + */ +function hook_field_collection_item_view($field_collection_item, $view_mode, $langcode) { + $field_collection_item->content['my_additional_field'] = array( + '#markup' => $additional_field, + '#weight' => 10, + '#theme' => 'mymodule_my_additional_field', + ); +} + +/** + * Alter the results of entity_view() for field collection items. + * + * This hook is called after the content has been assembled in a structured + * array and may be used for doing processing which requires that the complete + * field collection item content structure has been built. + * + * If the module wishes to act on the rendered HTML of the field collection item + * rather than the structured content array, it may use this hook to add a + * #post_render callback. See drupal_render() and theme() documentation + * respectively for details. + * + * @param $build + * A renderable array representing the field collection item content. + * + * @see hook_entity_view_alter() + */ +function hook_field_collection_item_view_alter($build) { + if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) { + // Change its weight. + $build['an_additional_field']['#weight'] = -10; + + // Add a #post_render callback to act on the rendered HTML of the entity. + $build['#post_render'][] = 'my_module_post_render'; + } +} + +/** + * @} + */ \ No newline at end of file diff --git a/sites/all/modules/field_collection/field_collection.info b/sites/all/modules/field_collection/field_collection.info new file mode 100644 index 0000000000000000000000000000000000000000..5e054b209c7a9a7d8df4a6aaf032b564e970a4e7 --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.info @@ -0,0 +1,16 @@ +name = Field collection +description = Provides a field collection field, to which any number of fields can be attached. +core = 7.x +dependencies[] = entity +files[] = field_collection.test +files[] = field_collection.info.inc +files[] = views/field_collection_handler_relationship.inc +configure = admin/structure/field-collections +package = Fields + +; Information added by drupal.org packaging script on 2012-12-25 +version = "7.x-1.0-beta5" +core = "7.x" +project = "field_collection" +datestamp = "1356475963" + diff --git a/sites/all/modules/field_collection/field_collection.info.inc b/sites/all/modules/field_collection/field_collection.info.inc new file mode 100644 index 0000000000000000000000000000000000000000..03c961a14364fa8730891a0343a37fd985f45278 --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.info.inc @@ -0,0 +1,30 @@ +<?php + +/** + * @file + * Provides entity property info for field collection items. + */ + +class FieldCollectionItemMetadataController extends EntityDefaultMetadataController { + + public function entityPropertyInfo() { + $info = parent::entityPropertyInfo(); + $properties = &$info['field_collection_item']['properties']; + + $properties['field_name']['label'] = t('Field name'); + $properties['field_name']['description'] = t('The machine-readable name of the field collection field containing this item.'); + $properties['field_name']['required'] = TRUE; + + $properties['host_entity'] = array( + 'label' => t('Host entity'), + 'type' => 'entity', + 'description' => t('The entity containing the field collection field.'), + 'getter callback' => 'field_collection_item_get_host_entity', + 'setter callback' => 'field_collection_item_set_host_entity', + 'required' => TRUE, + ); + + return $info; + } + +} \ No newline at end of file diff --git a/sites/all/modules/field_collection/field_collection.install b/sites/all/modules/field_collection/field_collection.install new file mode 100644 index 0000000000000000000000000000000000000000..c668b124ebbb13fbe5ab102615c8898ebf99899e --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.install @@ -0,0 +1,215 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the field_collection module. + */ + +/** + * Implements hook_schema(). + */ +function field_collection_schema() { + + $schema['field_collection_item'] = array( + 'description' => 'Stores information about field collection items.', + 'fields' => array( + 'item_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique field collection item ID.', + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Default revision ID.', + ), + 'field_name' => array( + 'description' => 'The name of the field on the host entity embedding this entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'archived' => array( + 'description' => 'Boolean indicating whether the field collection item is archived.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('item_id'), + ); + $schema['field_collection_item_revision'] = array( + 'description' => 'Stores revision information about field collection items.', + 'fields' => array( + 'revision_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique revision ID.', + ), + 'item_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Field collection item ID.', + ), + ), + 'primary key' => array('revision_id'), + 'indexes' => array( + 'item_id' => array('item_id'), + ), + 'foreign keys' => array( + 'versioned_field_collection_item' => array( + 'table' => 'field_collection_item', + 'columns' => array('item_id' => 'item_id'), + ), + ), + ); + return $schema; +} + +/** + * Implements hook_field_schema(). + */ +function field_collection_field_schema($field) { + $columns = array( + 'value' => array( + 'type' => 'int', + 'not null' => FALSE, + 'description' => 'The field collection item id.', + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => FALSE, + 'description' => 'The field collection item revision id.', + ), + ); + return array( + 'columns' => $columns, + ); +} + +/** + * Update the administer field collection permission machine name. + */ +function field_collection_update_7000() { + db_update('role_permission') + ->fields(array('permission' => 'administer field collections')) + ->condition('permission', 'administer field-collections') + ->execute(); +} + +/** + * Add revision support. + */ +function field_collection_update_7001() { + + // Add revision_id column to field_collection_item table. + $revision_id_spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Default revision ID.', + // Set default to 0 temporarily. + 'initial' => 0, + ); + db_add_field('field_collection_item', 'revision_id', $revision_id_spec); + + // Initialize the revision_id to be the same as the item_id. + db_update('field_collection_item') + ->expression('revision_id', 'item_id') + ->execute(); + + // Add the archived column + $archived_spec = array( + 'description' => 'Boolean indicating whether the field collection item is archived.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('field_collection_item', 'archived', $archived_spec); + + // Create the new table. It is important to explicitly define the schema here + // rather than use the hook_schema definition: http://drupal.org/node/150220. + $schema['field_collection_item_revision'] = array( + 'description' => 'Stores revision information about field collection items.', + 'fields' => array( + 'revision_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique revision ID.', + ), + 'item_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Field collection item ID.', + ), + ), + 'primary key' => array('revision_id'), + 'indexes' => array( + 'item_id' => array('item_id'), + ), + 'foreign keys' => array( + 'versioned_field_collection_item' => array( + 'table' => 'field_collection_item', + 'columns' => array('item_id' => 'item_id'), + ), + ), + ); + db_create_table('field_collection_item_revision', $schema['field_collection_item_revision']); + + // Fill the new table with the correct data. + $items = db_select('field_collection_item', 'fci') + ->fields('fci') + ->execute(); + foreach ($items as $item) { + // Update field_collection_item_revision table. + db_insert('field_collection_item_revision') + ->fields(array( + 'revision_id' => $item->item_id, + 'item_id' => $item->item_id, + )) + ->execute(); + } + + // Update the field_collection_field_schema columns for all tables. + foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { + $table_prefixes = array('field_data', 'field_revision'); + foreach ($table_prefixes as $table_prefix) { + + $table = sprintf('%s_%s', $table_prefix, $field_name); + $value_column = sprintf('%s_value', $field_name); + $revision_id_column = sprintf('%s_revision_id', $field_name); + + // Add a revision_id column. + $revision_id_spec['description'] = 'The field collection item revision id.'; + db_add_field($table, $revision_id_column, $revision_id_spec); + + // Initialize the revision_id to be the same as the item_id. + db_update($table) + ->expression($revision_id_column, $value_column) + ->execute(); + } + } + + // Need to get the system up-to-date so drupal_schema_fields_sql() will work. + $schema = drupal_get_schema('field_collection_item_revision', TRUE); +} + +/** + * Remove orphaned field collection item entities. + */ +function field_collection_update_7002() { + // Loop over all fields and delete any orphaned field collection items. + foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { + + $select = db_select('field_collection_item', 'fci') + ->fields('fci', array('item_id')) + ->condition('field_name', $field_name) + ->condition('archived', 0); + $select->leftJoin('field_data_' . $field_name, 'field', "field.{$field_name}_value = fci.item_id "); + $select->isNull('field.entity_id'); + $ids = $select->execute()->fetchCol(0); + + entity_delete_multiple('field_collection_item', $ids); + $count = count($ids); + drupal_set_message("Deleted $count orphaned field collection items."); + } +} diff --git a/sites/all/modules/field_collection/field_collection.module b/sites/all/modules/field_collection/field_collection.module new file mode 100644 index 0000000000000000000000000000000000000000..bf6a005e6b9378b84b58764c0555f6e1152ffc67 --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.module @@ -0,0 +1,1864 @@ +<?php + +/** + * @file + * Module implementing field collection field type. + */ + +/** + * Implements hook_help(). + */ +function field_collection_help($path, $arg) { + switch ($path) { + case 'admin/help#field_collection': + $output = ''; + $output .= '<h3>' . t('About') . '</h3>'; + $output .= '<p>' . t('The field collection module provides a field, to which any number of fields can be attached. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>'; + return $output; + } +} + +/** + * Implements hook_entity_info(). + */ +function field_collection_entity_info() { + $return['field_collection_item'] = array( + 'label' => t('Field collection item'), + 'label callback' => 'entity_class_label', + 'uri callback' => 'entity_class_uri', + 'entity class' => 'FieldCollectionItemEntity', + 'controller class' => 'EntityAPIController', + 'base table' => 'field_collection_item', + 'revision table' => 'field_collection_item_revision', + 'fieldable' => TRUE, + // For integration with Redirect module. + // @see http://drupal.org/node/1263884 + 'redirect' => FALSE, + 'entity keys' => array( + 'id' => 'item_id', + 'revision' => 'revision_id', + 'bundle' => 'field_name', + ), + 'module' => 'field_collection', + 'view modes' => array( + 'full' => array( + 'label' => t('Full content'), + 'custom settings' => FALSE, + ), + ), + 'access callback' => 'field_collection_item_access', + 'metadata controller class' => 'FieldCollectionItemMetadataController' + ); + + // Add info about the bundles. We do not use field_info_fields() but directly + // use field_read_fields() as field_info_fields() requires built entity info + // to work. + foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { + $return['field_collection_item']['bundles'][$field_name] = array( + 'label' => t('Field collection @field', array('@field' => $field_name)), + 'admin' => array( + 'path' => 'admin/structure/field-collections/%field_collection_field_name', + 'real path' => 'admin/structure/field-collections/' . strtr($field_name, array('_' => '-')), + 'bundle argument' => 3, + 'access arguments' => array('administer field collections'), + ), + ); + } + + return $return; +} + +/** + * Menu callback for loading the bundle names. + */ +function field_collection_field_name_load($arg) { + $field_name = strtr($arg, array('-' => '_')); + if (($field = field_info_field($field_name)) && $field['type'] == 'field_collection') { + return $field_name; + } +} + +/** + * Loads a field collection item. + * + * @return field_collection_item + * The field collection item entity or FALSE. + */ +function field_collection_item_load($item_id, $reset = FALSE) { + $result = field_collection_item_load_multiple(array($item_id), array(), $reset); + return $result ? reset($result) : FALSE; +} + +/** + * Loads a field collection revision. + * + * @param $revision_id + * The field collection revision ID. + */ +function field_collection_item_revision_load($revision_id) { + return entity_revision_load('field_collection_item', $revision_id); +} + +/** + * Loads field collection items. + * + * @return + * An array of field collection item entities. + */ +function field_collection_item_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('field_collection_item', $ids, $conditions, $reset); +} + +/** + * Class for field_collection_item entities. + */ +class FieldCollectionItemEntity extends Entity { + + /** + * Field collection field info. + * + * @var array + */ + protected $fieldInfo; + + /** + * The host entity object. + * + * @var object + */ + protected $hostEntity; + + /** + * The host entity ID. + * + * @var integer + */ + protected $hostEntityId; + + /** + * The host entity revision ID if this is not the default revision. + * + * @var integer + */ + protected $hostEntityRevisionId; + + /** + * The host entity type. + * + * @var string + */ + protected $hostEntityType; + + /** + * The language under which the field collection item is stored. + * + * @var string + */ + protected $langcode = LANGUAGE_NONE; + + /** + * Entity ID. + * + * @var integer + */ + public $item_id; + + /** + * Field collection revision ID. + * + * @var integer + */ + public $revision_id; + + /** + * The name of the field-collection field this item is associated with. + * + * @var string + */ + public $field_name; + + /** + * Whether this revision is the default revision. + * + * @var bool + */ + public $default_revision = TRUE; + + /** + * Whether the field collection item is archived, i.e. not in use. + * + * @see FieldCollectionItemEntity::isInUse() + * @var bool + */ + public $archived = FALSE; + + /** + * Constructs the entity object. + */ + public function __construct(array $values = array(), $entityType = NULL) { + parent::__construct($values, 'field_collection_item'); + // Workaround issues http://drupal.org/node/1084268 and + // http://drupal.org/node/1264440: + // Check if the required property is set before checking for the field's + // type. If the property is not set, we are hitting a PDO or a core's bug. + // FIXME: Remove when #1264440 is fixed and the required PHP version is + // properly identified and documented in the module documentation. + if (isset($this->field_name)) { + // Ok, we have the field name property, we can proceed and check the field's type + $field_info = $this->fieldInfo(); + if (!$field_info || $field_info['type'] != 'field_collection') { + throw new Exception("Invalid field name given: {$this->field_name} is not a Field Collection field."); + } + } + } + + /** + * Provides info about the field on the host entity, which embeds this + * field collection item. + */ + public function fieldInfo() { + return field_info_field($this->field_name); + } + + /** + * Provides info of the field instance containing the reference to this + * field collection item. + */ + public function instanceInfo() { + if ($this->fetchHostDetails()) { + return field_info_instance($this->hostEntityType(), $this->field_name, $this->hostEntityBundle()); + } + } + + /** + * Returns the field instance label translated to interface language. + */ + public function translatedInstanceLabel($langcode = NULL) { + if ($info = $this->instanceInfo()) { + if (module_exists('i18n_field')) { + return i18n_string("field:{$this->field_name}:{$info['bundle']}:label", $info['label'], array('langcode' => $langcode)); + } + return $info['label']; + } + } + + /** + * Specifies the default label, which is picked up by label() by default. + */ + public function defaultLabel() { + // @todo make configurable. + if ($this->fetchHostDetails()) { + $field = $this->fieldInfo(); + $label = $this->translatedInstanceLabel(); + + if ($field['cardinality'] == 1) { + return $label; + } + elseif ($this->item_id) { + return t('!instance_label @count', array('!instance_label' => $label, '@count' => $this->delta() + 1)); + } + else { + return t('New !instance_label', array('!instance_label' => $label)); + } + } + return t('Unconnected field collection item'); + } + + /** + * Returns the path used to view the entity. + */ + public function path() { + if ($this->item_id) { + return field_collection_field_get_path($this->fieldInfo()) . '/' . $this->item_id; + } + } + + /** + * Returns the URI as returned by entity_uri(). + */ + public function defaultUri() { + return array( + 'path' => $this->path(), + ); + } + + /** + * Sets the host entity. Only possible during creation of a item. + * + * @param $create_link + * (optional) Whether a field-item linking the host entity to the field + * collection item should be created. + */ + public function setHostEntity($entity_type, $entity, $langcode = LANGUAGE_NONE, $create_link = TRUE) { + if (!empty($this->is_new)) { + $this->hostEntityType = $entity_type; + $this->hostEntity = $entity; + $this->langcode = $langcode; + + list($this->hostEntityId, $this->hostEntityRevisionId) = entity_extract_ids($this->hostEntityType, $this->hostEntity); + // If the host entity is not saved yet, set the id to FALSE. So + // fetchHostDetails() does not try to load the host entity details. + if (!isset($this->hostEntityId)) { + $this->hostEntityId = FALSE; + } + // We are create a new field collection for a non-default entity, thus + // set archived to TRUE. + if (!entity_revision_is_default($entity_type, $entity)) { + $this->hostEntityId = FALSE; + $this->archived = TRUE; + } + if ($create_link) { + $entity->{$this->field_name}[$this->langcode][] = array('entity' => $this); + } + } + else { + throw new Exception('The host entity may be set only during creation of a field collection item.'); + } + } + + /** + * Returns the host entity, which embeds this field collection item. + */ + public function hostEntity() { + if ($this->fetchHostDetails()) { + if (!isset($this->hostEntity) && $this->isInUse()) { + $this->hostEntity = entity_load_single($this->hostEntityType, $this->hostEntityId); + } + elseif (!isset($this->hostEntity) && $this->hostEntityRevisionId) { + $this->hostEntity = entity_revision_load($this->hostEntityType, $this->hostEntityRevisionId); + } + return $this->hostEntity; + } + } + + /** + * Returns the entity type of the host entity, which embeds this + * field collection item. + */ + public function hostEntityType() { + if ($this->fetchHostDetails()) { + return $this->hostEntityType; + } + } + + /** + * Returns the id of the host entity, which embeds this field collection item. + */ + public function hostEntityId() { + if ($this->fetchHostDetails()) { + if (!$this->hostEntityId && $this->hostEntityRevisionId) { + $this->hostEntityId = entity_id($this->hostEntityType, $this->hostEntity()); + } + return $this->hostEntityId; + } + } + + /** + * Returns the bundle of the host entity, which embeds this field collection + * item. + */ + public function hostEntityBundle() { + if ($entity = $this->hostEntity()) { + list($id, $rev_id, $bundle) = entity_extract_ids($this->hostEntityType, $entity); + return $bundle; + } + } + + protected function fetchHostDetails() { + if (!isset($this->hostEntityId)) { + if ($this->item_id) { + // For saved field collections, query the field data to determine the + // right host entity. + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fieldInfo(), 'revision_id', $this->revision_id); + if (!$this->isInUse()) { + $query->age(FIELD_LOAD_REVISION); + } + $result = $query->execute(); + list($this->hostEntityType, $data) = each($result); + + if ($this->isInUse()) { + $this->hostEntityId = $data ? key($data) : FALSE; + $this->hostEntityRevisionId = FALSE; + } + // If we are querying for revisions, we get the revision ID. + else { + $this->hostEntityId = FALSE; + $this->hostEntityRevisionId = $data ? key($data) : FALSE; + } + } + else { + // No host entity available yet. + $this->hostEntityId = FALSE; + } + } + return !empty($this->hostEntityId) || !empty($this->hostEntity) || !empty($this->hostEntityRevisionId); + } + + /** + * Determines the $delta of the reference pointing to this field collection + * item. + */ + public function delta() { + if (($entity = $this->hostEntity()) && isset($entity->{$this->field_name})) { + foreach ($entity->{$this->field_name} as $langcode => &$data) { + foreach ($data as $delta => $item) { + if (isset($item['value']) && $item['value'] == $this->item_id) { + $this->langcode = $langcode; + return $delta; + } + elseif (isset($item['entity']) && $item['entity'] === $this) { + $this->langcode = $langcode; + return $delta; + } + } + } + } + } + + /** + * Determines the language code under which the item is stored. + */ + public function langcode() { + if ($this->delta() != NULL) { + return $this->langcode; + } + } + + /** + * Determines whether this field collection item revision is in use. + * + * Field collection items may be contained in from non-default host entity + * revisions. If the field collection item does not appear in the default + * host entity revision, the item is actually not used by default and so + * marked as 'archived'. + * If the field collection item appears in the default revision of the host + * entity, the default revision of the field collection item is in use there + * and the collection is not marked as archived. + */ + public function isInUse() { + return $this->default_revision && !$this->archived; + } + + /** + * Save the field collection item. + * + * By default, always save the host entity, so modules are able to react + * upon changes to the content of the host and any 'last updated' dates of + * entities get updated. + * + * For creating an item a host entity has to be specified via setHostEntity() + * before this function is invoked. For the link between the entities to be + * fully established, the host entity object has to be updated to include a + * reference on this field collection item during saving. So do not skip + * saving the host for creating items. + * + * @param $skip_host_save + * (internal) If TRUE is passed, the host entity is not saved automatically + * and therefore no link is created between the host and the item or + * revision updates might be skipped. Use with care. + */ + public function save($skip_host_save = FALSE) { + // Make sure we have a host entity during creation. + if (!empty($this->is_new) && !(isset($this->hostEntityId) || isset($this->hostEntity) || isset($this->hostEntityRevisionId))) { + throw new Exception("Unable to create a field collection item without a given host entity."); + } + + // Only save directly if we are told to skip saving the host entity. Else, + // we always save via the host as saving the host might trigger saving + // field collection items anyway (e.g. if a new revision is created). + if ($skip_host_save) { + return entity_get_controller($this->entityType)->save($this); + } + else { + $host_entity = $this->hostEntity(); + if (!$host_entity) { + throw new Exception("Unable to save a field collection item without a valid reference to a host entity."); + } + // If this is creating a new revision, also do so for the host entity. + if (!empty($this->revision) || !empty($this->is_new_revision)) { + $host_entity->revision = TRUE; + if (!empty($this->default_revision)) { + entity_revision_set_default($this->hostEntityType, $host_entity); + } + } + // Set the host entity reference, so the item will be saved with the host. + // @see field_collection_field_presave() + $delta = $this->delta(); + if (isset($delta)) { + $host_entity->{$this->field_name}[$this->langcode][$delta] = array('entity' => $this); + } + else { + $host_entity->{$this->field_name}[$this->langcode][] = array('entity' => $this); + } + return entity_save($this->hostEntityType, $host_entity); + } + } + + /** + * Deletes the field collection item and the reference in the host entity. + */ + public function delete() { + parent::delete(); + $this->deleteHostEntityReference(); + } + + /** + * Deletes the host entity's reference of the field collection item. + */ + protected function deleteHostEntityReference() { + $delta = $this->delta(); + if ($this->item_id && isset($delta)) { + unset($this->hostEntity->{$this->field_name}[$this->langcode][$delta]); + entity_save($this->hostEntityType, $this->hostEntity); + } + } + + /** + * Intelligently delete a field collection item revision. + * + * If a host entity is revisioned with its field collection items, deleting + * a field collection item on the default revision of the host should not + * delete the collection item from archived revisions too. Instead, we delete + * the current default revision and archive the field collection. + * + * If no revisions are left or the host is not revisionable, the whole item + * is deleted. + */ + public function deleteRevision($skip_host_update = FALSE) { + if (!$this->revision_id) { + return; + } + $info = entity_get_info($this->hostEntityType()); + if (empty($info['entity keys']['revision']) || !$this->hostEntity()) { + return $this->delete(); + } + if (!$skip_host_update) { + // Just remove the item from the host, which cares about deleting the + // item (depending on whether the update creates a new revision). + $this->deleteHostEntityReference(); + } + elseif (!$this->isDefaultRevision()) { + entity_revision_delete('field_collection_item', $this->revision_id); + } + // If deleting the default revision, take care! + else { + $row = db_select('field_collection_item_revision', 'r') + ->fields('r') + ->condition('item_id', $this->item_id) + ->condition('revision_id', $this->revision_id, '<>') + ->execute() + ->fetchAssoc(); + // If no other revision is left, delete. Else archive the item. + if (!$row) { + $this->delete(); + } + else { + // Make the other revision the default revision and archive the item. + db_update('field_collection_item') + ->fields(array('archived' => 1, 'revision_id' => $row['revision_id'])) + ->condition('item_id', $this->item_id) + ->execute(); + entity_get_controller('field_collection_item')->resetCache(array($this->item_id)); + entity_revision_delete('field_collection_item', $this->revision_id); + } + } + } + + /** + * Export the field collection item. + * + * Since field collection entities are not directly exportable (i.e., do not + * have 'exportable' set to TRUE in hook_entity_info()) and since Features + * calls this method when exporting the field collection as a field attached + * to another entity, we return the export in the format expected by + * Features, rather than in the normal Entity::export() format. + */ + public function export($prefix = '') { + // Based on code in EntityDefaultFeaturesController::export_render(). + $export = "entity_import('" . $this->entityType() . "', '"; + $export .= addcslashes(parent::export(), '\\\''); + $export .= "')"; + return $export; + } + + /** + * Magic method to only serialize what's necessary. + */ + public function __sleep() { + $vars = get_object_vars($this); + unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']); + unset($vars['fieldInfo']); + // Also do not serialize the host entity, but only if it has already an id. + if ($this->hostEntity && ($this->hostEntityId || $this->hostEntityRevisionId)) { + unset($vars['hostEntity']); + } + + // Also key the returned array with the variable names so the method may + // be easily overridden and customized. + return drupal_map_assoc(array_keys($vars)); + } + + /** + * Magic method to invoke setUp() on unserialization. + * + * @todo: Remove this once it appears in a released entity API module version. + */ + public function __wakeup() { + $this->setUp(); + } +} + +/** + * Implements hook_menu(). + */ +function field_collection_menu() { + $items = array(); + if (module_exists('field_ui')) { + $items['admin/structure/field-collections'] = array( + 'title' => 'Field collections', + 'description' => 'Manage fields on field collections.', + 'page callback' => 'field_collections_overview', + 'access arguments' => array('administer field collections'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'field_collection.admin.inc', + ); + } + + // Add menu paths for viewing/editing/deleting field collection items. + foreach (field_info_fields() as $field) { + if ($field['type'] == 'field_collection') { + $path = field_collection_field_get_path($field); + $count = count(explode('/', $path)); + + $items[$path . '/%field_collection_item'] = array( + 'page callback' => 'field_collection_item_page_view', + 'page arguments' => array($count), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('view', $count), + 'file' => 'field_collection.pages.inc', + ); + $items[$path . '/%field_collection_item/view'] = array( + 'title' => 'View', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items[$path . '/%field_collection_item/edit'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_collection_item_form', $count), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('update', $count), + 'title' => 'Edit', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'file' => 'field_collection.pages.inc', + ); + $items[$path . '/%field_collection_item/delete'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_collection_item_delete_confirm', $count), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('delete', $count), + 'title' => 'Delete', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'file' => 'field_collection.pages.inc', + ); + // Add entity type and the entity id as additional arguments. + $items[$path . '/add/%/%'] = array( + 'page callback' => 'field_collection_item_add', + 'page arguments' => array($field['field_name'], $count + 1, $count + 2), + // The pace callback takes care of checking access itself. + 'access callback' => TRUE, + 'file' => 'field_collection.pages.inc', + ); + // Add menu items for dealing with revisions. + $items[$path . '/%field_collection_item/revisions/%field_collection_item_revision'] = array( + 'page callback' => 'field_collection_item_page_view', + 'page arguments' => array($count + 2), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('view', $count + 2), + 'file' => 'field_collection.pages.inc', + ); + } + } + + $items['field_collection/ajax'] = array( + 'title' => 'Remove item callback', + 'page callback' => 'field_collection_remove_js', + 'delivery callback' => 'ajax_deliver', + 'access callback' => TRUE, + 'theme callback' => 'ajax_base_page_theme', + 'type' => MENU_CALLBACK, + 'file path' => 'includes', + 'file' => 'form.inc', + ); + + return $items; +} + +/** + * Implements hook_menu_alter() to fix the field collections admin UI tabs. + */ +function field_collection_menu_alter(&$items) { + if (module_exists('field_ui') && isset($items['admin/structure/field-collections/%field_collection_field_name/fields'])) { + // Make the fields task the default local task. + $items['admin/structure/field-collections/%field_collection_field_name'] = $items['admin/structure/field-collections/%field_collection_field_name/fields']; + $item = &$items['admin/structure/field-collections/%field_collection_field_name']; + $item['type'] = MENU_NORMAL_ITEM; + $item['title'] = 'Manage fields'; + $item['title callback'] = 'field_collection_admin_page_title'; + $item['title arguments'] = array(3); + + $items['admin/structure/field-collections/%field_collection_field_name/fields'] = array( + 'title' => 'Manage fields', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 1, + ); + } +} + +/** + * Menu title callback. + */ +function field_collection_admin_page_title($field_name) { + return t('Field collection @field_name', array('@field_name' => $field_name)); +} + +/** + * Implements hook_admin_paths(). + */ +function field_collection_admin_paths() { + if (variable_get('node_admin_theme')) { + return array( + 'field-collection/*/*/edit' => TRUE, + 'field-collection/*/*/delete' => TRUE, + 'field-collection/*/add/*/*' => TRUE, + ); + } +} + +/** + * Implements hook_permission(). + */ +function field_collection_permission() { + return array( + 'administer field collections' => array( + 'title' => t('Administer field collections'), + 'description' => t('Create and delete fields on field collections.'), + ), + ); +} + +/** + * Determines whether the given user has access to a field collection. + * + * @param $op + * The operation being performed. One of 'view', 'update', 'create', 'delete'. + * @param $item + * Optionally a field collection item. If nothing is given, access for all + * items is determined. + * @param $account + * The user to check for. Leave it to NULL to check for the global user. + * @return boolean + * Whether access is allowed or not. + */ +function field_collection_item_access($op, FieldCollectionItemEntity $item = NULL, $account = NULL) { + // We do not support editing field collection revisions that are not used at + // the hosts default revision as saving the host might result in a new default + // revision. + if (isset($item) && !$item->isInUse() && $op != 'view') { + return FALSE; + } + if (user_access('administer field collections', $account)) { + return TRUE; + } + if (!isset($item)) { + return FALSE; + } + $op = $op == 'view' ? 'view' : 'edit'; + // Access is determined by the entity and field containing the reference. + $field = field_info_field($item->field_name); + $entity_access = entity_access($op == 'view' ? 'view' : 'update', $item->hostEntityType(), $item->hostEntity(), $account); + return $entity_access && field_access($op, $field, $item->hostEntityType(), $item->hostEntity(), $account); +} + +/** + * Implements hook_theme(). + */ +function field_collection_theme() { + return array( + 'field_collection_item' => array( + 'render element' => 'elements', + 'template' => 'field-collection-item', + ), + 'field_collection_view' => array( + 'render element' => 'element', + ), + ); +} + +/** + * Implements hook_field_info(). + */ +function field_collection_field_info() { + return array( + 'field_collection' => array( + 'label' => t('Field collection'), + 'description' => t('This field stores references to embedded entities, which itself may contain any number of fields.'), + 'instance_settings' => array(), + 'default_widget' => 'field_collection_hidden', + 'default_formatter' => 'field_collection_view', + // As of now there is no UI for setting the path. + 'settings' => array( + 'path' => '', + 'hide_blank_items' => TRUE, + ), + // Add entity property info. + 'property_type' => 'field_collection_item', + 'property_callbacks' => array('field_collection_entity_metadata_property_callback'), + ), + ); +} + +/** + * Implements hook_field_instance_settings_form(). + */ +function field_collection_field_instance_settings_form($field, $instance) { + + $element['fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Default value'), + '#collapsible' => FALSE, + // As field_ui_default_value_widget() does, we change the #parents so that + // the value below is writing to $instance in the right location. + '#parents' => array('instance'), + ); + // Be sure to set the default value to NULL, e.g. to repair old fields + // that still have one. + $element['fieldset']['default_value'] = array( + '#type' => 'value', + '#value' => NULL, + ); + $element['fieldset']['content'] = array( + '#pre' => '<p>', + '#markup' => t('To specify a default value, configure it via the regular default value setting of each field that is part of the field collection. To do so, go to the <a href="!url">Manage fields</a> screen of the field collection.', array('!url' => url('admin/structure/field-collections/' . strtr($field['field_name'], array('_' => '-')) . '/fields'))), + '#suffix' => '</p>', + ); + return $element; +} + +/** + * Returns the base path to use for field collection items. + */ +function field_collection_field_get_path($field) { + if (empty($field['settings']['path'])) { + return 'field-collection/' . strtr($field['field_name'], array('_' => '-')); + } + return $field['settings']['path']; +} + +/** + * Implements hook_field_settings_form(). + */ +function field_collection_field_settings_form($field, $instance) { + + $form['hide_blank_items'] = array( + '#type' => 'checkbox', + '#title' => t('Hide blank items'), + '#default_value' => $field['settings']['hide_blank_items'], + '#description' => t("A blank item is always added to any multivalued field's form. If checked, any additional blank items are hidden except of the first item which is always shown."), + '#weight' => 10, + '#states' => array( + // Hide the setting if the cardinality is 1. + 'invisible' => array( + ':input[name="field[cardinality]"]' => array('value' => '1'), + ), + ), + ); + return $form; +} + +/** + * Implements hook_field_presave(). + * + * Support saving field collection items in @code $item['entity'] @endcode. This + * may be used to seamlessly create field collection items during host-entity + * creation or to save changes to the host entity and its collections at once. + */ +function field_collection_field_presave($host_entity_type, $host_entity, $field, $instance, $langcode, &$items) { + foreach ($items as &$item) { + // In case the entity has been changed / created, save it and set the id. + // If the host entity creates a new revision, save new item-revisions as + // well. + if (isset($item['entity']) || !empty($host_entity->revision)) { + + if ($entity = field_collection_field_get_entity($item)) { + + if (!empty($entity->is_new)) { + $entity->setHostEntity($host_entity_type, $host_entity, LANGUAGE_NONE, FALSE); + } + + // If the host entity is saved as new revision, do the same for the item. + if (!empty($host_entity->revision)) { + $entity->revision = TRUE; + $is_default = entity_revision_is_default($host_entity_type, $host_entity); + // If an entity type does not support saving non-default entities, + // assume it will be saved as default. + if (!isset($is_default) || $is_default) { + $entity->default_revision = TRUE; + $entity->archived = FALSE; + } + } + $entity->save(TRUE); + + $item = array( + 'value' => $entity->item_id, + 'revision_id' => $entity->revision_id, + ); + } + } + } +} + +/** + * Implements hook_field_update(). + * + * Care about removed field collection items. + */ +function field_collection_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { + $items_original = !empty($entity->original->{$field['field_name']}[$langcode]) ? $entity->original->{$field['field_name']}[$langcode] : array(); + $original_by_id = array_flip(field_collection_field_item_to_ids($items_original)); + + foreach ($items as $item) { + unset($original_by_id[$item['value']]); + } + + // If there are removed items, care about deleting the item entities. + if ($original_by_id) { + $ids = array_flip($original_by_id); + + // If we are creating a new revision, the old-items should be kept but get + // marked as archived now. + if (!empty($entity->revision)) { + db_update('field_collection_item') + ->fields(array('archived' => 1)) + ->condition('item_id', $ids, 'IN') + ->execute(); + } + else { + // Delete unused field collection items now. + foreach (field_collection_item_load_multiple($ids) as $item) { + $item->deleteRevision(TRUE); + } + } + } +} + +/** + * Implements hook_field_delete(). + */ +function field_collection_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Also delete all embedded entities. + if ($ids = field_collection_field_item_to_ids($items)) { + // We filter out entities that are still being referenced by other + // host-entities. This should never be the case, but it might happened e.g. + // when modules cloned a node without knowing about field-collection. + $entity_info = entity_get_info($entity_type); + $entity_id_name = $entity_info['entity keys']['id']; + $field_column = key($field['columns']); + + foreach ($ids as $id_key => $id) { + $query = new EntityFieldQuery(); + $entities = $query + ->fieldCondition($field['field_name'], $field_column, $id) + ->execute(); + unset($entities[$entity_type][$entity->$entity_id_name]); + + if (!empty($entities[$entity_type])) { + // Filter this $id out. + unset($ids[$id_key]); + } + } + + entity_delete_multiple('field_collection_item', $ids); + } +} + +/** + * Implements hook_field_delete_revision(). + */ +function field_collection_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) { + foreach ($items as $item) { + if (!empty($item['revision_id'])) { + if ($entity = field_collection_item_revision_load($item['revision_id'])) { + $entity->deleteRevision(TRUE); + } + } + } +} + +/** + * Get an array of field collection item IDs stored in the given field items. + */ +function field_collection_field_item_to_ids($items) { + $ids = array(); + foreach ($items as $item) { + if (!empty($item['value'])) { + $ids[] = $item['value']; + } + } + return $ids; +} + +/** + * Implements hook_field_is_empty(). + */ +function field_collection_field_is_empty($item, $field) { + if (!empty($item['value'])) { + return FALSE; + } + elseif (isset($item['entity'])) { + return field_collection_item_is_empty($item['entity']); + } + return TRUE; +} + +/** + * Determines whether a field collection item entity is empty based on the collection-fields. + */ +function field_collection_item_is_empty(FieldCollectionItemEntity $item) { + $instances = field_info_instances('field_collection_item', $item->field_name); + $is_empty = TRUE; + + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + + // Determine the list of languages to iterate on. + $languages = field_available_languages('field_collection_item', $field); + + foreach ($languages as $langcode) { + if (!empty($item->{$field_name}[$langcode])) { + // If at least one collection-field is not empty; the + // field collection item is not empty. + foreach ($item->{$field_name}[$langcode] as $field_item) { + if (!module_invoke($field['module'], 'field_is_empty', $field_item, $field)) { + $is_empty = FALSE; + } + } + } + } + } + + // Allow other modules a chance to alter the value before returning. + drupal_alter('field_collection_is_empty', $is_empty, $item); + return $is_empty; +} + +/** + * Implements hook_field_formatter_info(). + */ +function field_collection_field_formatter_info() { + return array( + 'field_collection_list' => array( + 'label' => t('Links to field collection items'), + 'field types' => array('field_collection'), + 'settings' => array( + 'edit' => t('Edit'), + 'delete' => t('Delete'), + 'add' => t('Add'), + 'description' => TRUE, + ), + ), + 'field_collection_view' => array( + 'label' => t('Field collection items'), + 'field types' => array('field_collection'), + 'settings' => array( + 'edit' => t('Edit'), + 'delete' => t('Delete'), + 'add' => t('Add'), + 'description' => TRUE, + 'view_mode' => 'full', + ), + ), + 'field_collection_fields' => array( + 'label' => t('Fields only'), + 'field types' => array('field_collection'), + 'settings' => array( + 'view_mode' => 'full', + ), + ), + ); +} + +/** + * Implements hook_field_formatter_settings_form(). + */ +function field_collection_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $elements = array(); + + if ($display['type'] != 'field_collection_fields') { + $elements['edit'] = array( + '#type' => 'textfield', + '#title' => t('Edit link title'), + '#default_value' => $settings['edit'], + '#description' => t('Leave the title empty, to hide the link.'), + ); + $elements['delete'] = array( + '#type' => 'textfield', + '#title' => t('Delete link title'), + '#default_value' => $settings['delete'], + '#description' => t('Leave the title empty, to hide the link.'), + ); + $elements['add'] = array( + '#type' => 'textfield', + '#title' => t('Add link title'), + '#default_value' => $settings['add'], + '#description' => t('Leave the title empty, to hide the link.'), + ); + $elements['description'] = array( + '#type' => 'checkbox', + '#title' => t('Show the field description beside the add link.'), + '#default_value' => $settings['description'], + '#description' => t('If enabled and the add link is shown, the field description is shown in front of the add link.'), + ); + } + + // Add a select form element for view_mode if viewing the rendered field_collection. + if ($display['type'] !== 'field_collection_list') { + + $entity_type = entity_get_info('field_collection_item'); + $options = array(); + foreach ($entity_type['view modes'] as $mode => $info) { + $options[$mode] = $info['label']; + } + + $elements['view_mode'] = array( + '#type' => 'select', + '#title' => t('View mode'), + '#options' => $options, + '#default_value' => $settings['view_mode'], + '#description' => t('Select the view mode'), + ); + } + + return $elements; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function field_collection_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $output = array(); + + if ($display['type'] !== 'field_collection_fields') { + $links = array_filter(array_intersect_key($settings, array_flip(array('add', 'edit', 'delete')))); + if ($links) { + $output[] = t('Links: @links', array('@links' => check_plain(implode(', ', $links)))); + } + else { + $output[] = t('Links: none'); + } + } + + if ($display['type'] !== 'field_collection_list') { + $entity_type = entity_get_info('field_collection_item'); + if (!empty($entity_type['view modes'][$settings['view_mode']]['label'])) { + $output[] = t('View mode: @mode', array('@mode' => $entity_type['view modes'][$settings['view_mode']]['label'])); + } + } + + return implode('<br>', $output); +} + +/** + * Implements hook_field_formatter_view(). + */ +function field_collection_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + $settings = $display['settings']; + + switch ($display['type']) { + case 'field_collection_list': + + foreach ($items as $delta => $item) { + if ($field_collection = field_collection_field_get_entity($item)) { + $output = l($field_collection->label(), $field_collection->path()); + $links = array(); + foreach (array('edit', 'delete') as $op) { + if ($settings[$op] && field_collection_item_access($op == 'edit' ? 'update' : $op, $field_collection)) { + $title = entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_$op", $settings[$op]); + $links[] = l($title, $field_collection->path() . '/' . $op, array('query' => drupal_get_destination())); + } + } + if ($links) { + $output .= ' (' . implode('|', $links) . ')'; + } + $element[$delta] = array('#markup' => $output); + } + } + field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display); + break; + + case 'field_collection_view': + + $element['#attached']['css'][] = drupal_get_path('module', 'field_collection') . '/field_collection.theme.css'; + $view_mode = !empty($display['settings']['view_mode']) ? $display['settings']['view_mode'] : 'full'; + foreach ($items as $delta => $item) { + if ($field_collection = field_collection_field_get_entity($item)) { + $element[$delta]['entity'] = $field_collection->view($view_mode); + $element[$delta]['#theme_wrappers'] = array('field_collection_view'); + $element[$delta]['#attributes']['class'][] = 'field-collection-view'; + $element[$delta]['#attributes']['class'][] = 'clearfix'; + $element[$delta]['#attributes']['class'][] = drupal_clean_css_identifier('view-mode-' . $view_mode); + + $links = array( + '#theme' => 'links__field_collection_view', + ); + $links['#attributes']['class'][] = 'field-collection-view-links'; + foreach (array('edit', 'delete') as $op) { + if ($settings[$op] && field_collection_item_access($op == 'edit' ? 'update' : $op, $field_collection)) { + $links['#links'][$op] = array( + 'title' => entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_$op", $settings[$op]), + 'href' => $field_collection->path() . '/' . $op, + 'query' => drupal_get_destination(), + ); + } + } + $element[$delta]['links'] = $links; + } + } + field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display); + break; + + case 'field_collection_fields': + + $view_mode = !empty($display['settings']['view_mode']) ? $display['settings']['view_mode'] : 'full'; + foreach ($items as $delta => $item) { + if ($field_collection = field_collection_field_get_entity($item)) { + $element[$delta]['entity'] = $field_collection->view($view_mode); + } + } + break; + } + + return $element; +} + +/** + * Helper function to add links to a field collection field. + */ +function field_collection_field_formatter_links(&$element, $entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $settings = $display['settings']; + + if ($settings['add'] && ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || count($items) < $field['cardinality'])) { + // Check whether the current is allowed to create a new item. + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field['field_name'])); + $field_collection_item->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + + if (field_collection_item_access('create', $field_collection_item)) { + $path = field_collection_field_get_path($field); + list($id) = entity_extract_ids($entity_type, $entity); + $element['#suffix'] = ''; + if (!empty($settings['description'])) { + $element['#suffix'] .= '<div class="description field-collection-description">' . field_filter_xss($instance['description']) . '</div>'; + } + $title = entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_add", $settings['add']); + $add_path = $path . '/add/' . $entity_type . '/' . $id; + $element['#suffix'] .= '<ul class="action-links action-links-field-collection-add"><li>'; + $element['#suffix'] .= l($title, $add_path, array('query' => drupal_get_destination())); + $element['#suffix'] .= '</li></ul>'; + } + } + // If there is no add link, add a special class to the last item. + if (empty($element['#suffix'])) { + $index = count(element_children($element)) - 1; + $element[$index]['#attributes']['class'][] = 'field-collection-view-final'; + } + + $element += array('#prefix' => '', '#suffix' => ''); + $element['#prefix'] .= '<div class="field-collection-container clearfix">'; + $element['#suffix'] .= '</div>'; + + return $element; +} + +/** + * Themes field collection items printed using the field_collection_view formatter. + */ +function theme_field_collection_view($variables) { + $element = $variables['element']; + return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>'; +} + +/** + * Implements hook_field_widget_info(). + */ +function field_collection_field_widget_info() { + return array( + 'field_collection_hidden' => array( + 'label' => t('Hidden'), + 'field types' => array('field_collection'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + ), + 'field_collection_embed' => array( + 'label' => t('Embedded'), + 'field types' => array('field_collection'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + ), + ); +} + +/** + * Implements hook_field_widget_form(). + */ +function field_collection_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + static $recursion = 0; + + switch ($instance['widget']['type']) { + case 'field_collection_hidden': + return $element; + + case 'field_collection_embed': + // If the field collection item form contains another field collection, + // we might ran into a recursive loop. Prevent that. + if ($recursion++ > 3) { + drupal_set_message(t('The field collection item form has not been embedded to avoid recursive loops.'), 'error'); + return $element; + } + $field_parents = $element['#field_parents']; + $field_name = $element['#field_name']; + $language = $element['#language']; + + // Nest the field collection item entity form in a dedicated parent space, + // by appending [field_name, langcode, delta] to the current parent space. + // That way the form values of the field collection item are separated. + $parents = array_merge($field_parents, array($field_name, $language, $delta)); + + $element += array( + '#element_validate' => array('field_collection_field_widget_embed_validate'), + '#parents' => $parents, + ); + + if ($field['cardinality'] == 1) { + $element['#type'] = 'fieldset'; + } + + $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state); + + if (!empty($field['settings']['hide_blank_items']) && $delta == $field_state['items_count'] && $delta > 0) { + // Do not add a blank item. Also see + // field_collection_field_attach_form() for correcting #max_delta. + $recursion--; + return FALSE; + } + elseif (!empty($field['settings']['hide_blank_items']) && $field_state['items_count'] == 0) { + // We show one item, so also specify that as item count. So when the + // add button is pressed the item count will be 2 and we show to items. + $field_state['items_count'] = 1; + } + + if (isset($field_state['entity'][$delta])) { + $field_collection_item = $field_state['entity'][$delta]; + } + else { + if (isset($items[$delta])) { + $field_collection_item = field_collection_field_get_entity($items[$delta], $field_name); + } + // Show an empty collection if we have no existing one or it does not + // load. + if (empty($field_collection_item)) { + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_name)); + } + + // Put our entity in the form state, so FAPI callbacks can access it. + $field_state['entity'][$delta] = $field_collection_item; + } + + field_form_set_state($field_parents, $field_name, $language, $form_state, $field_state); + field_attach_form('field_collection_item', $field_collection_item, $element, $form_state, $language); + + if (empty($element['#required'])) { + $element['#after_build'][] = 'field_collection_field_widget_embed_delay_required_validation'; + } + + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) { + $element['remove_button'] = array( + '#delta' => $delta, + '#name' => implode('_', $parents) . '_remove_button', + '#type' => 'submit', + '#value' => t('Remove'), + '#validate' => array(), + '#submit' => array('field_collection_remove_submit'), + '#limit_validation_errors' => array(), + '#ajax' => array( + 'path' => 'field_collection/ajax', + 'effect' => 'fade', + ), + '#weight' => 1000, + ); + } + + $recursion--; + return $element; + } +} + +/** + * Implements hook_field_attach_form(). + * + * Corrects #max_delta when we hide the blank field collection item. + * + * @see field_add_more_js() + * @see field_collection_field_widget_form() + */ +function field_collection_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { + + foreach (field_info_instances($entity_type, $form['#bundle']) as $field_name => $instance) { + $field = field_info_field($field_name); + + if ($field['type'] == 'field_collection' && $field['settings']['hide_blank_items'] + && field_access('edit', $field, $entity_type) && $instance['widget']['type'] == 'field_collection_embed') { + + $element_langcode = $form[$field_name]['#language']; + if ($form[$field_name][$element_langcode]['#max_delta'] > 0) { + $form[$field_name][$element_langcode]['#max_delta']--; + } + } + } +} + +/** + * Page callback to handle AJAX for removing a field collection item. + * + * This is a direct page callback. The actual job of deleting the item is + * done in the submit handler for the button, so all we really need to + * do is process the form and then generate output. We generate this + * output by doing a replace command on the id of the entire form element. + */ +function field_collection_remove_js() { + // drupal_html_id() very helpfully ensures that all html IDS are unique + // on a page. Unfortunately what it doesn't realize is that the IDs + // we are generating are going to replace IDs that already exist, so + // this actually works against us. + if (isset($_POST['ajax_html_ids'])) { + unset($_POST['ajax_html_ids']); + } + + list($form, $form_state) = ajax_get_form(); + drupal_process_form($form['#form_id'], $form, $form_state); + + // Get the information on what we're removing. + $button = $form_state['triggering_element']; + // Go two levels up in the form, to the whole widget. + $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -3)); + // Now send back the proper AJAX command to replace it. + $return = array( + '#type' => 'ajax', + '#commands' => array( + ajax_command_replace('#' . $element['#id'], drupal_render($element)) + ), + ); + + // Because we're doing this ourselves, messages aren't automatic. We have + // to add them. + $messages = theme('status_messages'); + if ($messages) { + $return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages); + } + + return $return; +} + +/** + * Submit callback to remove an item from the field UI multiple wrapper. + * + * When a remove button is submitted, we need to find the item that it + * referenced and delete it. Since field UI has the deltas as a straight + * unbroken array key, we have to renumber everything down. Since we do this + * we *also* need to move all the deltas around in the $form_state['values'] + * and $form_state['input'] so that user changed values follow. This is a bit + * of a complicated process. + */ +function field_collection_remove_submit($form, &$form_state) { + $button = $form_state['triggering_element']; + $delta = $button['#delta']; + + // Where in the form we'll find the parent element. + $address = array_slice($button['#array_parents'], 0, -2); + + // Go one level up in the form, to the widgets container. + $parent_element = drupal_array_get_nested_value($form, $address); + $field_name = $parent_element['#field_name']; + $langcode = $parent_element['#language']; + $parents = $parent_element['#field_parents']; + + $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); + + // Go ahead and renumber everything from our delta to the last + // item down one. This will overwrite the item being removed. + for ($i = $delta; $i <= $field_state['items_count']; $i++) { + $old_element_address = array_merge($address, array($i + 1)); + $new_element_address = array_merge($address, array($i)); + + $moving_element = drupal_array_get_nested_value($form, $old_element_address); + $moving_element_value = drupal_array_get_nested_value($form_state['values'], $old_element_address); + $moving_element_input = drupal_array_get_nested_value($form_state['input'], $old_element_address); + + // Tell the element where it's being moved to. + $moving_element['#parents'] = $new_element_address; + + // Move the element around. + form_set_value($moving_element, $moving_element_value, $form_state); + drupal_array_set_nested_value($form_state['input'], $moving_element['#parents'], $moving_element_input); + + // Move the entity in our saved state. + if (isset($field_state['entity'][$i + 1])) { + $field_state['entity'][$i] = $field_state['entity'][$i + 1]; + } + else { + unset($field_state['entity'][$i]); + } + } + + // Replace the deleted entity with an empty one. This helps to ensure that + // trying to add a new entity won't ressurect a deleted entity from the + // trash bin. + $count = count($field_state['entity']); + $field_state['entity'][$count] = entity_create('field_collection_item', array('field_name' => $field_name)); + + // Then remove the last item. But we must not go negative. + if ($field_state['items_count'] > 0) { + $field_state['items_count']--; + } + + // Fix the weights. Field UI lets the weights be in a range of + // (-1 * item_count) to (item_count). This means that when we remove one, + // the range shrinks; weights outside of that range then get set to + // the first item in the select by the browser, floating them to the top. + // We use a brute force method because we lost weights on both ends + // and if the user has moved things around, we have to cascade because + // if I have items weight weights 3 and 4, and I change 4 to 3 but leave + // the 3, the order of the two 3s now is undefined and may not match what + // the user had selected. + $input = drupal_array_get_nested_value($form_state['input'], $address); + // Sort by weight + uasort($input, '_field_sort_items_helper'); + + // Reweight everything in the correct order. + $weight = -1 * $field_state['items_count']; + foreach ($input as $key => $item) { + if ($item) { + $input[$key]['_weight'] = $weight++; + } + } + + drupal_array_set_nested_value($form_state['input'], $address, $input); + field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); + + $form_state['rebuild'] = TRUE; +} + +/** + * Gets a field collection item entity for a given field item. + * + * @param $field_name + * (optional) If given and there is no entity yet, a new entity object is + * created for the given item. + * + * @return + * The entity object or FALSE. + */ +function field_collection_field_get_entity(&$item, $field_name = NULL) { + if (isset($item['entity'])) { + return $item['entity']; + } + elseif (isset($item['value'])) { + // By default always load the default revision, so caches get used. + $entity = field_collection_item_load($item['value']); + if ($entity->revision_id != $item['revision_id']) { + // A non-default revision is a referenced, so load this one. + $entity = field_collection_item_revision_load($item['revision_id']); + } + return $entity; + } + elseif (!isset($item['entity']) && isset($field_name)) { + $item['entity'] = entity_create('field_collection_item', array('field_name' => $field_name)); + return $item['entity']; + } + return FALSE; +} + +/** + * FAPI #after_build of an individual field collection element to delay the validation of #required. + */ +function field_collection_field_widget_embed_delay_required_validation(&$element, &$form_state) { + // If the process_input flag is set, the form and its input is going to be + // validated. Prevent #required (sub)fields from throwing errors while + // their non-#required field collection item is empty. + if ($form_state['process_input']) { + _field_collection_collect_required_elements($element, $element['#field_collection_required_elements']); + } + return $element; +} + +function _field_collection_collect_required_elements(&$element, &$required_elements) { + // Recurse through all children. + foreach (element_children($element) as $key) { + if (isset($element[$key]) && $element[$key]) { + _field_collection_collect_required_elements($element[$key], $required_elements); + } + } + if (!empty($element['#required'])) { + $element['#required'] = FALSE; + $required_elements[] = &$element; + $element += array('#pre_render' => array()); + array_unshift($element['#pre_render'], 'field_collection_field_widget_render_required'); + } +} + +/** + * #pre_render callback that ensures the element is rendered as being required. + */ +function field_collection_field_widget_render_required($element) { + $element['#required'] = TRUE; + return $element; +} + +/** + * FAPI validation of an individual field collection element. + */ +function field_collection_field_widget_embed_validate($element, &$form_state, $complete_form) { + $instance = field_widget_instance($element, $form_state); + $field = field_widget_field($element, $form_state); + $field_parents = $element['#field_parents']; + $field_name = $element['#field_name']; + $language = $element['#language']; + + $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state); + $field_collection_item = $field_state['entity'][$element['#delta']]; + + // Attach field API validation of the embedded form. + field_attach_form_validate('field_collection_item', $field_collection_item, $element, $form_state); + + // Now validate required elements if the entity is not empty. + if (!field_collection_item_is_empty($field_collection_item) && !empty($element['#field_collection_required_elements'])) { + foreach ($element['#field_collection_required_elements'] as &$elements) { + + // Copied from _form_validate(). + if (isset($elements['#needs_validation'])) { + $is_empty_multiple = (!count($elements['#value'])); + $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); + $is_empty_value = ($elements['#value'] === 0); + if ($is_empty_multiple || $is_empty_string || $is_empty_value) { + if (isset($elements['#title'])) { + form_error($elements, t('!name field is required.', array('!name' => $elements['#title']))); + } + else { + form_error($elements); + } + } + } + } + } + + // Only if the form is being submitted, finish the collection entity and + // prepare it for saving. + if ($form_state['submitted'] && !form_get_errors()) { + + field_attach_submit('field_collection_item', $field_collection_item, $element, $form_state); + + // Load initial form values into $item, so any other form values below the + // same parents are kept. + $item = drupal_array_get_nested_value($form_state['values'], $element['#parents']); + + // Set the _weight if it is a multiple field. + if (isset($element['_weight']) && ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED)) { + $item['_weight'] = $element['_weight']['#value']; + } + + // Put the field collection item in $item['entity'], so it is saved with + // the host entity via hook_field_presave() / field API if it is not empty. + // @see field_collection_field_presave() + $item['entity'] = $field_collection_item; + form_set_value($element, $item, $form_state); + } +} + +/** + * Implements hook_field_create_field(). + */ +function field_collection_field_create_field($field) { + if ($field['type'] == 'field_collection') { + field_attach_create_bundle('field_collection_item', $field['field_name']); + + // Clear caches. + entity_info_cache_clear(); + // Do not directly issue menu rebuilds here to avoid potentially multiple + // rebuilds. Instead, let menu_get_item() issue the rebuild on the next + // request. + variable_set('menu_rebuild_needed', TRUE); + } +} + +/** + * Implements hook_field_delete_field(). + */ +function field_collection_field_delete_field($field) { + if ($field['type'] == 'field_collection') { + // Notify field.module that field collection was deleted. + field_attach_delete_bundle('field_collection_item', $field['field_name']); + + // Clear caches. + entity_info_cache_clear(); + // Do not directly issue menu rebuilds here to avoid potentially multiple + // rebuilds. Instead, let menu_get_item() issue the rebuild on the next + // request. + variable_set('menu_rebuild_needed', TRUE); + } +} + +/** + * Implements hook_i18n_string_list_{textgroup}_alter(). + */ +function field_collection_i18n_string_list_field_alter(&$properties, $type, $instance) { + if ($type == 'field_instance') { + $field = field_info_field($instance['field_name']); + + if ($field['type'] == 'field_collection' && !empty($instance['display'])) { + + foreach ($instance['display'] as $view_mode => $display) { + if ($display['type'] != 'field_collection_fields') { + $display['settings'] += array('edit' => 'edit', 'delete' => 'delete', 'add' => 'add'); + + $properties['field'][$instance['field_name']][$instance['bundle']]['setting_edit'] = array( + 'title' => t('Edit link title'), + 'string' => $display['settings']['edit'], + ); + $properties['field'][$instance['field_name']][$instance['bundle']]['setting_delete'] = array( + 'title' => t('Delete link title'), + 'string' => $display['settings']['delete'], + ); + $properties['field'][$instance['field_name']][$instance['bundle']]['setting_add'] = array( + 'title' => t('Add link title'), + 'string' => $display['settings']['add'], + ); + } + } + } + } +} + +/** + * Implements hook_views_api(). + */ +function field_collection_views_api() { + return array( + 'api' => '3.0-alpha1', + 'path' => drupal_get_path('module', 'field_collection') . '/views', + ); +} + +/** + * Implements hook_features_pipe_component_alter() for fields. + */ +function field_collection_features_pipe_field_alter(&$pipe, $data, $export) { + // Add the fields of the field collection entity to the pipe. + foreach ($data as $identifier) { + if (($field = features_field_load($identifier)) && $field['field_config']['type'] == 'field_collection') { + $fields = field_info_instances('field_collection_item', $field['field_config']['field_name']); + foreach ($fields as $name => $field) { + $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; + } + } + } +} + +/** + * Callback for generating entity metadata property info for our field instances. + * + * @see field_collection_field_info() + */ +function field_collection_entity_metadata_property_callback(&$info, $entity_type, $field, $instance, $field_type) { + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + // Set the bundle as we know it is the name of the field. + $property['bundle'] = $field['field_name']; + $property['getter callback'] = 'field_collection_field_property_get'; +} + +/** + * Entity property info setter callback for the host entity property. + * + * As the property is of type entity, the value will be passed as a wrapped + * entity. + */ +function field_collection_item_set_host_entity($item, $property_name, $wrapper) { + if (empty($item->is_new)) { + throw new EntityMetadataWrapperException('The host entity may be set only during creation of a field collection item.'); + } + if (!isset($wrapper->{$item->field_name})) { + throw new EntityMetadataWrapperException('The specified entity has no such field collection field.'); + } + $item->setHostEntity($wrapper->type(), $wrapper->value()); +} + +/** + * Entity property info getter callback for the host entity property. + */ +function field_collection_item_get_host_entity($item) { + // As the property is defined as 'entity', we have to return a wrapped entity. + return entity_metadata_wrapper($item->hostEntityType(), $item->hostEntity()); +} + +/** + * Entity property info getter callback for the field collection items. + * + * Like entity_metadata_field_property_get(), but additionally supports getting + * not-yet saved collection items from @code $item['entity'] @endcode. + */ +function field_collection_field_property_get($entity, array $options, $name, $entity_type, $info) { + $field = field_info_field($name); + $langcode = field_language($entity_type, $entity, $name, isset($options['language']) ? $options['language']->language : NULL); + $values = array(); + if (isset($entity->{$name}[$langcode])) { + foreach ($entity->{$name}[$langcode] as $delta => $data) { + // Wrappers do not support multiple entity references being revisions or + // not yet saved entities. In the case of a single reference we can return + // the entity object though. + if ($field['cardinality'] == 1) { + $values[$delta] = field_collection_field_get_entity($data); + } + elseif (isset($data['value'])) { + $values[$delta] = $data['value']; + } + } + } + // For an empty single-valued field, we have to return NULL. + return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values; +} + +/** + * Implements hook_devel_generate(). + */ +function field_collection_devel_generate($object, $field, $instance, $bundle) { + // Create a new field collection object and add fake data to its fields. + $field_collection = entity_create('field_collection_item', array('field_name' => $field['field_name'])); + $field_collection->language = $object->language; + $field_collection->setHostEntity($instance['entity_type'], $object, $object->language, FALSE); + + devel_generate_fields($field_collection, 'field_collection_item', $field['field_name']); + + $field_collection->save(TRUE); + + return array('value' => $field_collection->item_id); +} diff --git a/sites/all/modules/field_collection/field_collection.pages.inc b/sites/all/modules/field_collection/field_collection.pages.inc new file mode 100644 index 0000000000000000000000000000000000000000..6e692662dd803a2cd5e64c0f7060fc6348e34f8c --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.pages.inc @@ -0,0 +1,138 @@ +<?php + +/** + * @file + * Provides the field collection item view / edit / delete pages. + */ + +// TODO: fix being embedded in a host with revisions. + +/** + * Field collection item view page. + */ +function field_collection_item_page_view($field_collection_item) { + // @todo: Set breadcrumb including the host. + drupal_set_title($field_collection_item->label()); + return $field_collection_item->view('full', NULL, TRUE); +} + +/** + * Form for editing a field collection item. + * @todo implement hook_forms(). + */ +function field_collection_item_form($form, &$form_state, $field_collection_item) { + if (!isset($field_collection_item->is_new)) { + drupal_set_title($field_collection_item->label()); + } + $form_state += array('field_collection_item' => $field_collection_item); + + // Hack: entity_form_field_validate() needs the bundle to be set. + // @todo: Fix core and remove the hack. + $form['field_name'] = array('#type' => 'value', '#value' => $field_collection_item->field_name); + + field_attach_form('field_collection_item', $field_collection_item, $form, $form_state); + + $form['actions'] = array('#type' => 'actions', '#weight' => 50); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 5, + ); + return $form; +} + +/** + * Validation callback. + */ +function field_collection_item_form_validate($form, &$form_state) { + entity_form_field_validate('field_collection_item', $form, $form_state); +} + +/** + * Submit builder. Extracts the form values and updates the entity. + */ +function field_collection_item_form_submit_build_field_collection($form, $form_state) { + entity_form_submit_build_entity('field_collection_item', $form_state['field_collection_item'], $form, $form_state); + return $form_state['field_collection_item']; +} + +/** + * Submit callback that permanently saves the changes to the entity. + */ +function field_collection_item_form_submit($form, &$form_state) { + $field_collection_item = field_collection_item_form_submit_build_field_collection($form, $form_state); + $field_collection_item->save(); + drupal_set_message(t('The changes have been saved.')); + $form_state['redirect'] = $field_collection_item->path(); +} + +/** + * Form for deleting a field collection item. + */ +function field_collection_item_delete_confirm($form, &$form_state, $field_collection_item) { + $form_state += array('field_collection_item' => $field_collection_item); + return confirm_form($form, + t('Are you sure you want to delete %label?', array('%label' => $field_collection_item->label())), + $field_collection_item->path(), + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + +/** + * Submit callback for deleting a field collection item. + */ +function field_collection_item_delete_confirm_submit($form, &$form_state) { + $field_collection_item = $form_state['field_collection_item']; + $field_collection_item->deleteRevision(); + drupal_set_message(t('%label has been deleted.', array('%label' => drupal_ucfirst($field_collection_item->label())))); + $form_state['redirect'] = '<front>'; +} + +/** + * Add a new field collection item. + * + * @todo: Support optionally passing in the revision_id and langcode parameters. + */ +function field_collection_item_add($field_name, $entity_type, $entity_id, $revision_id = NULL, $langcode = NULL) { + $info = entity_get_info(); + if (!isset($info[$entity_type])) { + return MENU_NOT_FOUND; + } + $result = entity_load($entity_type, array($entity_id)); + $entity = reset($result); + if (!$entity) { + return MENU_NOT_FOUND; + } + // Ensure the given entity is of a bundle that has an instance of the field. + list($id, $rev_id, $bundle) = entity_extract_ids($entity_type, $entity); + $instance = field_info_instance($entity_type, $field_name, $bundle); + if (!$instance) { + return MENU_NOT_FOUND; + } + + // Check field cardinality. + $field = field_info_field($field_name); + $langcode = LANGUAGE_NONE; + if (!($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !isset($entity->{$field_name}[$langcode]) || count($entity->{$field_name}[$langcode]) < $field['cardinality'])) { + drupal_set_message(t('Too many items.'), 'error'); + return ''; + } + + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_name)); + // Do not link the field collection item with the host entity at this point, + // as during the form-workflow we have multiple field collection item entity + // instances, which we don't want link all with the host. + // That way the link is going to be created when the item is saved. + $field_collection_item->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + + $title = ($field['cardinality'] == 1) ? $instance['label'] : t('Add new !instance_label', array('!instance_label' => $field_collection_item->translatedInstanceLabel())); + drupal_set_title($title); + + // Make sure the current user has access to create a field collection item. + if (!field_collection_item_access('create', $field_collection_item)) { + return MENU_ACCESS_DENIED; + } + return drupal_get_form('field_collection_item_form', $field_collection_item); +} diff --git a/sites/all/modules/field_collection/field_collection.test b/sites/all/modules/field_collection/field_collection.test new file mode 100644 index 0000000000000000000000000000000000000000..b62d048736fa3b4f9711b2fac839e425a7ce24fa --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.test @@ -0,0 +1,422 @@ +<?php + +/** + * @file + * field_collections tests. + */ + +/** + * Test basics. + */ +class FieldCollectionBasicTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Field collection', + 'description' => 'Tests creating and using field collections.', + 'group' => 'Field types', + ); + } + + function setUp() { + parent::setUp('field_collection'); + + // Create a field_collection field to use for the tests. + $this->field_name = 'field_test_collection'; + $this->field = array('field_name' => $this->field_name, 'type' => 'field_collection', 'cardinality' => 4); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array(), + 'widget' => array( + 'type' => 'hidden', + 'label' => 'Test', + 'settings' => array(), + ), + ); + $this->instance = field_create_instance($this->instance); + } + + /** + * Helper for creating a new node with a field collection item. + */ + protected function createNodeWithFieldCollection() { + $node = $this->drupalCreateNode(array('type' => 'article')); + // Manually create a field_collection. + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + $entity->save(); + + return array($node, $entity); + } + + /** + * Tests CRUD. + */ + function testCRUD() { + list ($node, $entity) = $this->createNodeWithFieldCollection(); + $node = node_load($node->nid, NULL, TRUE); + $this->assertEqual($entity->item_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['value'], 'A field_collection has been successfully created and referenced.'); + $this->assertEqual($entity->revision_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id'], 'A field_collection has been successfully created and referenced.'); + + // Test adding an additional field_collection during node edit. + $entity2 = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $node->{$this->field_name}[LANGUAGE_NONE][] = array('entity' => $entity2); + node_save($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(!empty($entity2->item_id) && !empty($entity2->revision_id), 'Field_collection has been saved.'); + $this->assertEqual($entity->item_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['value'], 'Existing reference has been kept during update.'); + $this->assertEqual($entity->revision_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id'], 'Existing reference has been kept during update (revision).'); + $this->assertEqual($entity2->item_id, $node->{$this->field_name}[LANGUAGE_NONE][1]['value'], 'New field_collection has been properly referenced'); + $this->assertEqual($entity2->revision_id, $node->{$this->field_name}[LANGUAGE_NONE][1]['revision_id'], 'New field_collection has been properly referenced (revision)'); + + // Make sure deleting the field_collection removes the reference. + $entity2->delete(); + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(!isset($node->{$this->field_name}[LANGUAGE_NONE][1]), 'Reference correctly deleted.'); + + // Make sure field_collections are removed during deletion of the host. + node_delete($node->nid); + $this->assertTrue(entity_load('field_collection_item', FALSE) === array(), 'Field collections are deleted when the host is deleted.'); + + // Try deleting nodes with collections without any values. + $node = $this->drupalCreateNode(array('type' => 'article')); + node_delete($node->nid); + $this->assertTrue(node_load($node->nid, NULL, TRUE) == FALSE, 'Node without collection values deleted.'); + + // Test creating a field collection entity with a not-yet saved host entity. + $node = entity_create('node', array('type' => 'article')); + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + $entity->save(); + // Now the node should have been saved with the collection and the link + // should have been established. + $this->assertTrue(!empty($node->nid), 'Node has been saved with the collection.'); + $this->assertTrue(count($node->{$this->field_name}[LANGUAGE_NONE]) == 1 && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['value']) && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']), 'Link has been established.'); + + // Again, test creating a field collection with a not-yet saved host entity, + // but this time save both entities via the host. + $node = entity_create('node', array('type' => 'article')); + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + node_save($node); + $this->assertTrue(!empty($entity->item_id) && !empty($entity->revision_id), 'Collection has been saved with the host.'); + $this->assertTrue(count($node->{$this->field_name}[LANGUAGE_NONE]) == 1 && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['value']) && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']), 'Link has been established.'); + + // Test Revisions. + list ($node, $item) = $this->createNodeWithFieldCollection(); + + // Test saving a new revision of a node. + $node->revision = TRUE; + node_save($node); + $item_updated = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $this->assertNotEqual($item->revision_id, $item_updated->revision_id, 'Creating a new host entity revision creates a new field collection revision.'); + + // Test saving the node without creating a new revision. + $item = $item_updated; + $node->revision = FALSE; + node_save($node); + $item_updated = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $this->assertEqual($item->revision_id, $item_updated->revision_id, 'Updating a new host entity without creating a new revision does not create a new field collection revision.'); + + // Create a new revision of the node, such we have a non default node and + // field collection revision. Then test using it. + $vid = $node->vid; + $item_revision_id = $item_updated->revision_id; + $node->revision = TRUE; + node_save($node); + + $item_updated = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $this->assertNotEqual($item_revision_id, $item_updated->revision_id, 'Creating a new host entity revision creates a new field collection revision.'); + $this->assertTrue($item_updated->isDefaultRevision(), 'Field collection of default host entity revision is default too.'); + $this->assertEqual($item_updated->hostEntityId(), $node->nid, 'Can access host entity ID of default field collection revision.'); + $this->assertEqual($item_updated->hostEntity()->vid, $node->vid, 'Loaded default host entity revision.'); + + $item = entity_revision_load('field_collection_item', $item_revision_id); + $this->assertFalse($item->isDefaultRevision(), 'Field collection of non-default host entity is non-default too.'); + $this->assertEqual($item->hostEntityId(), $node->nid, 'Can access host entity ID of non-default field collection revision.'); + $this->assertEqual($item->hostEntity()->vid, $vid, 'Loaded non-default host entity revision.'); + + // Delete the non-default revision and make sure the field collection item + // revision has been deleted too. + entity_revision_delete('node', $vid); + $this->assertFalse(entity_revision_load('node', $vid), 'Host entity revision deleted.'); + $this->assertFalse(entity_revision_load('field_collection_item', $item_revision_id), 'Field collection item revision deleted.'); + + // Test having archived field collections, i.e. collections referenced only + // in non-default revisions. + list ($node, $item) = $this->createNodeWithFieldCollection(); + // Create two revisions. + $node_vid = $node->vid; + $node->revision = TRUE; + node_save($node); + + $node_vid2 = $node->vid; + $node->revision = TRUE; + node_save($node); + + // Now delete the field collection item for the default revision. + $item = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $item_revision_id = $item->revision_id; + $item->deleteRevision(); + $node = node_load($node->nid); + $this->assertTrue(!isset($node->{$this->field_name}[LANGUAGE_NONE][0]), 'Field collection item revision removed from host.'); + $this->assertFalse(field_collection_item_revision_load($item->revision_id), 'Field collection item default revision deleted.'); + + $item = field_collection_item_load($item->item_id); + $this->assertNotEqual($item->revision_id, $item_revision_id, 'Field collection default revision has been updated.'); + $this->assertTrue($item->archived, 'Field collection item has been archived.'); + $this->assertFalse($item->isInUse(), 'Field collection item specified as not in use.'); + $this->assertTrue($item->isDefaultRevision(), 'Field collection of non-default host entity is default (but archived).'); + $this->assertEqual($item->hostEntityId(), $node->nid, 'Can access host entity ID of non-default field collection revision.'); + $this->assertEqual($item->hostEntity()->nid, $node->nid, 'Loaded non-default host entity revision.'); + + // Test deleting a revision of an archived field collection. + $node_revision2 = node_load($node->nid, $node_vid2); + $item = field_collection_item_revision_load($node_revision2->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']); + $item->deleteRevision(); + // There should be one revision left, so the item should still exist. + $item = field_collection_item_load($item->item_id); + $this->assertTrue($item->archived, 'Field collection item is still archived.'); + $this->assertFalse($item->isInUse(), 'Field collection item specified as not in use.'); + + // Test that deleting the first node revision deletes the whole field + // collection item as it contains its last revision. + node_revision_delete($node_vid); + $this->assertFalse(field_collection_item_load($item->item_id), 'Archived field collection deleted when last revision deleted.'); + + // Test that removing a field-collection item also deletes it. + list ($node, $item) = $this->createNodeWithFieldCollection(); + + $node->{$this->field_name}[LANGUAGE_NONE] = array(); + $node->revision = FALSE; + node_save($node); + $this->assertFalse(field_collection_item_load($item->item_id), 'Removed field collection item has been deleted.'); + + // Test removing a field-collection item while creating a new host revision. + list ($node, $item) = $this->createNodeWithFieldCollection(); + $node->{$this->field_name}[LANGUAGE_NONE] = array(); + $node->revision = TRUE; + node_save($node); + // Item should not be deleted but archived now. + $item = field_collection_item_load($item->item_id); + $this->assertTrue($item, 'Removed field collection item still exists.'); + $this->assertTrue($item->archived, 'Removed field collection item is archived.'); + } + + /** + * Make sure the basic UI and access checks are working. + */ + function testBasicUI() { + // Add a field to the collection. + $field = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'cardinality' => 1, + 'translatable' => FALSE, + ); + field_create_field($field); + $instance = array( + 'entity_type' => 'field_collection_item', + 'field_name' => 'field_text', + 'bundle' => $this->field_name, + 'label' => 'Test text field', + 'widget' => array( + 'type' => 'text_textfield', + ), + ); + field_create_instance($instance); + + $user = $this->drupalCreateUser(); + $node = $this->drupalCreateNode(array('type' => 'article')); + + $this->drupalLogin($user); + // Make sure access is denied. + $path = 'field-collection/field-test-collection/add/node/' . $node->nid; + $this->drupalGet($path); + $this->assertText(t('Access denied'), 'Access has been denied.'); + + $user_privileged = $this->drupalCreateUser(array('access content', 'edit any article content')); + $this->drupalLogin($user_privileged); + $this->drupalGet("node/$node->nid"); + $this->assertLinkByHref($path, 0, 'Add link is shown.'); + $this->drupalGet($path); + $this->assertText(t('Test text field'), 'Add form is shown.'); + + $edit['field_text[und][0][value]'] = $this->randomName(); + $this->drupalPost($path, $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Field collection saved.'); + + $this->assertText($edit['field_text[und][0][value]'], "Added field value is shown."); + + $edit['field_text[und][0][value]'] = $this->randomName(); + $this->drupalPost('field-collection/field-test-collection/1/edit', $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Field collection saved.'); + $this->assertText($edit['field_text[und][0][value]'], "Field collection has been edited."); + + $this->drupalGet('field-collection/field-test-collection/1'); + $this->assertText($edit['field_text[und][0][value]'], "Field collection can be viewed."); + + // Add further 3 items, so we have reached 4 == maxium cardinality. + $this->drupalPost($path, $edit, t('Save')); + $this->drupalPost($path, $edit, t('Save')); + $this->drupalPost($path, $edit, t('Save')); + // Make sure adding doesn't work any more as we have restricted cardinality + // to 1. + $this->drupalGet($path); + $this->assertText(t('Too many items.'), 'Maxium cardinality has been reached.'); + + $this->drupalPost('field-collection/field-test-collection/1/delete', array(), t('Delete')); + $this->drupalGet($path); + // Add form is shown again. + $this->assertText(t('Test text field'), 'Field collection item has been deleted.'); + + // Test the viewing a revision. There should be no links to change it. + $vid = $node->vid; + $node = node_load($node->nid, NULL, TRUE); + $node->revision = TRUE; + node_save($node); + + $this->drupalGet("node/$node->nid/revisions/$vid/view"); + $this->assertResponse(403, 'Access to view revision denied'); + // Login in as admin and try again. + $user = $this->drupalCreateUser(array('administer nodes', 'bypass node access')); + $this->drupalLogin($user); + $this->drupalGet("node/$node->nid/revisions/$vid/view"); + $this->assertNoResponse(403, 'Access to view revision granted'); + + $this->assertNoLinkByHref($path, 'No links on revision view.'); + $this->assertNoLinkByHref('field-collection/field-test-collection/2/edit', 'No links on revision view.'); + $this->assertNoLinkByHref('field-collection/field-test-collection/2/delete', 'No links on revision view.'); + + $this->drupalGet("node/$node->nid/revisions"); + } +} + + +/** + * Test using field collection with Rules. + */ +class FieldCollectionRulesIntegrationTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Field collection Rules integration', + 'description' => 'Tests using field collections with rules.', + 'group' => 'Field types', + 'dependencies' => array('rules'), + ); + } + + function setUp() { + parent::setUp(array('field_collection', 'rules')); + variable_set('rules_debug_log', 1); + } + + protected function createFields($cardinality = 4) { + // Create a field_collection field to use for the tests. + $this->field_name = 'field_test_collection'; + $this->field = array('field_name' => $this->field_name, 'type' => 'field_collection', 'cardinality' => $cardinality); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array(), + 'widget' => array( + 'type' => 'hidden', + 'label' => 'Test', + 'settings' => array(), + ), + ); + $this->instance = field_create_instance($this->instance); + // Add a field to the collection. + $field = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'cardinality' => 1, + 'translatable' => FALSE, + ); + field_create_field($field); + $instance = array( + 'entity_type' => 'field_collection_item', + 'field_name' => 'field_text', + 'bundle' => $this->field_name, + 'label' => 'Test text field', + 'widget' => array( + 'type' => 'text_textfield', + ), + ); + field_create_instance($instance); + } + + /** + * Test creation field collection items. + */ + function testCreation() { + $this->createFields(); + + $node = $this->drupalCreateNode(array('type' => 'article')); + // Create a field collection. + $action_set = rules_action_set(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $action_set->action('entity_create', array( + 'type' => 'field_collection_item', + 'param_field_name' => $this->field_name, + 'param_host_entity:select' => 'node', + )); + $action_set->action('data_set', array('data:select' => 'entity-created:field-text', 'value' => 'foo')); + $action_set->execute($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(!empty($node->{$this->field_name}[LANGUAGE_NONE][0]['value']), 'A field_collection has been successfully created.'); + $this->assertTrue(!empty($node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']), 'A field_collection has been successfully created (revision).'); + + // Now try making use of the field collection in rules. + $action_set = rules_action_set(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $action_set->action('drupal_message', array('message:select' => 'node:field-test-collection:0:field-text')); + $action_set->execute($node); + + $msg = drupal_get_messages(); + $this->assertEqual(array_pop($msg['status']), 'foo', 'Field collection can be used.'); + RulesLog::logger()->checkLog(); + } + + /** + * Test using field collection items via the host while they are being created. + */ + function testUsageDuringCreation() { + // Test using a single-cardinality field collection. + $this->createFields(1); + + $node = $this->drupalCreateNode(array('type' => 'article')); + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + // Now the field collection is linked to the host, but not yet saved. + + // Test using the wrapper on it. + $wrapper = entity_metadata_wrapper('node', $node); + $wrapper->get($this->field_name)->field_text->set('foo'); + $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], 'foo', 'Field collection item used during creation via the wrapper.'); + + // Now test it via Rules, which should save our changes. + $set = rules_action_set(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $set->action('data_set', array('data:select' => 'node:' . $this->field_name . ':field-text', 'value' => 'bar')); + $set->execute($node); + $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], 'bar', 'Field collection item used during creation via Rules.'); + $this->assertTrue(!empty($entity->item_id) && !empty($entity->revision_id), 'Field collection item has been saved by Rules and the host entity.'); + RulesLog::logger()->checkLog(); + } +} diff --git a/sites/all/modules/field_collection/field_collection.theme.css b/sites/all/modules/field_collection/field_collection.theme.css new file mode 100644 index 0000000000000000000000000000000000000000..b619185c3841a828c2efe5ac570a2f786e8ed919 --- /dev/null +++ b/sites/all/modules/field_collection/field_collection.theme.css @@ -0,0 +1,66 @@ +@CHARSET "UTF-8"; + +.field-collection-container { + border-bottom: 1px solid #D3D7D9; + margin-bottom: 1em; +} + +.field-collection-container .field-items .field-item { + margin-bottom: 10px; +} + +.field-collection-container .field-items .field-items .field-item { + margin-bottom: 0; +} + +.field-collection-view { + padding: 1em 0 0.3em 0; + margin: 0 1em 0 1em; + border-bottom: 1px dotted #D3D7D9; +} + +/* If there is no add link, don't show the final border. */ +.field-collection-view-final { + border-bottom: none; +} + +.field-collection-view .entity-field-collection-item { + float: left; +} + +.field-collection-view ul.field-collection-view-links { + float: right; + font-size: 0.821em; + list-style-type: none; + width: auto; + margin: 0 1em; + padding: 0; +} + +.field-collection-view .field-label { + width: 25%; +} + +.field-collection-view .content { + margin-top: 0; + width: 100%; +} + +.field-collection-view .entity-field-collection-item { + width: 100%; +} + +ul.field-collection-view-links li { + float: left; +} + +ul.field-collection-view-links li a { + margin-right: 1em; +} + +.field-collection-container ul.action-links-field-collection-add { + float: right; + padding: 0 0.5em 0 0; + margin: 0 0 1em 2em; + font-size: 0.821em; +} diff --git a/sites/all/modules/field_collection/views/field_collection.views.inc b/sites/all/modules/field_collection/views/field_collection.views.inc new file mode 100644 index 0000000000000000000000000000000000000000..a4e8a0d9ea8de871f7efc65afa651da549c6992c --- /dev/null +++ b/sites/all/modules/field_collection/views/field_collection.views.inc @@ -0,0 +1,53 @@ +<?php + +/** + * Implements hook_field_views_data(). + * + * Views integration for field collection fields. Adds a relationship to the + * default field data. + * + * @see field_views_field_default_views_data() + */ +function field_collection_field_views_data($field) { + $data = field_views_field_default_views_data($field); + + foreach ($data as $table_name => $table_data) { + foreach ($table_data as $field_name => $field_data) { + // Only operate on the "field_api_field_name"_value column. + if (strrpos($field_name, '_value') === (strlen($field_name) - strlen('_value'))) { + $data[$table_name][$field_name]['relationship'] = array( + 'handler' => 'field_collection_handler_relationship', + 'base' => 'field_collection_item', + 'base field' => 'item_id', + 'label' => t('field collection item from !field_name', array('!field_name' => $field['field_name'])), + 'field_name' => $field['field_name'], + ); + } + } + } + + foreach ($field['bundles'] as $entity_type => $bundles) { + $entity_info = entity_get_info($entity_type); + $pseudo_field_name = $field['field_name'] . '_' . $entity_type; + + list($label, $all_labels) = field_views_field_label($field['field_name']); + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + + $data['field_collection_item'][$pseudo_field_name]['relationship'] = array( + 'title' => t('Entity with the @field (@field_name)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])), + 'help' => t('Relate each @entity using @field.', array('@entity' => $entity, '@field' => $label)), + 'handler' => 'views_handler_relationship_entity_reverse', + 'field_name' => $field['field_name'], + 'field table' => _field_sql_storage_tablename($field), + 'field field' => $field['field_name'] . '_value', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('!field_name', array('!field_name' => $field['field_name'])), + ); + } + + return $data; +} diff --git a/sites/all/modules/field_collection/views/field_collection_handler_relationship.inc b/sites/all/modules/field_collection/views/field_collection_handler_relationship.inc new file mode 100644 index 0000000000000000000000000000000000000000..afbe121710290209ce1d07f245e89c37c0f0afd4 --- /dev/null +++ b/sites/all/modules/field_collection/views/field_collection_handler_relationship.inc @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Provide relationship handler for field collection fields. + */ +class field_collection_handler_relationship extends views_handler_relationship { + + function option_definition() { + $options = parent::option_definition(); + $options['delta'] = array('default' => -1); + + return $options; + } + + /** + * Add a delta selector for multiple fields. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $field = field_info_field($this->definition['field_name']); + + // Only add the delta selector if the field is multiple. + if ($field['cardinality']) { + $max_delta = ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) ? 10 : $field['cardinality']; + + $options = array('-1' => t('All')); + for ($i = 0; $i < $max_delta; $i++) { + $options[$i] = $i + 1; + } + $form['delta'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => $this->options['delta'], + '#title' => t('Delta'), + '#description' => t('The delta allows you to select which item in a multiple value field to key the relationship off of. Select "1" to use the first item, "2" for the second item, and so on. If you select "All", each item in the field will create a new row, which may appear to cause duplicates.'), + ); + } + } + + function ensure_my_table() { + $field = field_info_field($this->definition['field_name']); + + if (!isset($this->table_alias)) { + $join = $this->get_join(); + if ($this->options['delta'] != -1 && $field['cardinality']) { + $join->extra[] = array( + 'field' => 'delta', + 'value' => $this->options['delta'], + 'numeric' => TRUE, + ); + } + $this->table_alias = $this->query->add_table($this->table, $this->relationship, $join); + } + return $this->table_alias; + } +} diff --git a/sites/all/modules/form_builder/examples/form_builder_examples.info b/sites/all/modules/form_builder/examples/form_builder_examples.info index 373b881017af301f7b7a6eefe90db30ca7dd0f92..8523dd5388c94e0d0a1f40a390a9278d06152154 100644 --- a/sites/all/modules/form_builder/examples/form_builder_examples.info +++ b/sites/all/modules/form_builder/examples/form_builder_examples.info @@ -3,9 +3,9 @@ description = Form builder support for CCK, Webform, and Profile modules. core = 7.x dependencies[] = form_builder -; Information added by drupal.org packaging script on 2012-03-08 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2012-09-27 +version = "7.x-1.3" core = "7.x" project = "form_builder" -datestamp = "1331181344" +datestamp = "1348708781" diff --git a/sites/all/modules/form_builder/examples/form_builder_examples.module b/sites/all/modules/form_builder/examples/form_builder_examples.module index 4766f8bfcbc3d31e64f502ddb79764726533a0d2..e3407b2dee42635207589cce27ead16fd213f819 100644 --- a/sites/all/modules/form_builder/examples/form_builder_examples.module +++ b/sites/all/modules/form_builder/examples/form_builder_examples.module @@ -446,10 +446,10 @@ function form_builder_examples_export_recurse($form, $parents = array()) { } else { if (($property == '#title') || ($property == '#description')) { - $output .= " '". $property . "' => t('" . $form[$property] ."'),\n"; + $output .= " '". $property . "' => t('" . str_replace("'", "\'", $form[$property]) ."'),\n"; } else { - $output .= " '". $property . "' => '" . $form[$property] ."',\n"; + $output .= " '". $property . "' => '" . str_replace("'", "\'", $form[$property]) ."',\n"; } } } diff --git a/sites/all/modules/form_builder/form_builder.info b/sites/all/modules/form_builder/form_builder.info index 20e893b27fe3630a13c2d0c58aa738d3f0e0f475..7a0ed0db0e1f64cd9d4e937cf2c7ab170012c1ff 100644 --- a/sites/all/modules/form_builder/form_builder.info +++ b/sites/all/modules/form_builder/form_builder.info @@ -3,9 +3,9 @@ description = Form building framework. dependencies[] = options_element core = 7.x -; Information added by drupal.org packaging script on 2012-03-08 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2012-09-27 +version = "7.x-1.3" core = "7.x" project = "form_builder" -datestamp = "1331181344" +datestamp = "1348708781" diff --git a/sites/all/modules/form_builder/form_builder.install b/sites/all/modules/form_builder/form_builder.install index 04d5e5bfc4de132b061dffab71815526f26273cf..c7e691e6e83e3ee52bc4b2820b8a3cb9c4394892 100644 --- a/sites/all/modules/form_builder/form_builder.install +++ b/sites/all/modules/form_builder/form_builder.install @@ -21,6 +21,7 @@ function form_builder_requirements($phase) { if (empty($form_builder_types)) { $requirements['form_builder_types']['title'] = $t('Form builder'); $requirements['form_builder_types']['severity'] = REQUIREMENT_ERROR; + $requirements['form_builder_types']['value'] = $t('No dependent modules found.'); $requirements['form_builder_types']['description'] = t('Form builder module is installed but no modules implement support for it. You may want to disable Form builder module until it is needed.'); } } diff --git a/sites/all/modules/form_builder/form_builder.js b/sites/all/modules/form_builder/form_builder.js index 5968e7fadba4ce554fccfa6495369f21ac7d55cc..f49159093abb785690ade4f683cdd6bfb6cd5714 100644 --- a/sites/all/modules/form_builder/form_builder.js +++ b/sites/all/modules/form_builder/form_builder.js @@ -546,8 +546,11 @@ Drupal.formBuilder.addElement = function(response) { // Set the variable stating we're done updating. Drupal.formBuilder.updatingElement = false; - // Insert the new position form containing the new element. + // Insert the new position form containing the new element, but maintain + // the existing form action. + var positionAction = $('#form-builder-positions').attr('action'); $('#form-builder-positions').replaceWith(response.positionForm); + $('#form-builder-positions').attr('action', positionAction); // Submit the new positions form to save the new element position. Drupal.formBuilder.updateElementPosition($new.get(0)); diff --git a/sites/all/modules/form_builder/includes/form_builder.admin.inc b/sites/all/modules/form_builder/includes/form_builder.admin.inc index 5f0e1a2b2cf63481eb92863d3910144f2ec70a3d..478ec3afd31f237c47cc16e09f23e9b81f7578c9 100644 --- a/sites/all/modules/form_builder/includes/form_builder.admin.inc +++ b/sites/all/modules/form_builder/includes/form_builder.admin.inc @@ -67,14 +67,14 @@ function form_builder_add_page($form_type, $form_id, $element_type) { if (isset($_REQUEST['js'])) { $element = form_builder_cache_field_load($form_type, $form_id, $element_id); - $preview_form = form_builder_cache_load($form_type, $form_id); + $form_cache = form_builder_cache_load($form_type, $form_id); $data = array( 'formType' => $form_type, 'formId' => $form_id, 'elementId' => $element_id, 'html' => form_builder_field_render($form_type, $form_id, $element_id), - 'positionForm' => drupal_render(drupal_get_form('form_builder_positions', $preview_form, $form_type, $form_id)), + 'positionForm' => drupal_render(drupal_get_form('form_builder_positions', $form_cache, $form_type, $form_id)), ); form_builder_json_output($data); @@ -147,7 +147,7 @@ function form_builder_field_palette() { $groups = module_invoke_all('form_builder_palette_groups'); // TODO: We shouldn't have to clear the cache here. $form = form_builder_cache_load($active['form_type'], $active['form_id'], NULL, TRUE); - $active_fields = form_builder_get_element_ids($form); + $active_fields = form_builder_get_element_types($form); foreach ($fields as $key => $field) { if ($field['unique'] && in_array($key, $active_fields)) { $fields[$key]['in_use'] = TRUE; @@ -199,6 +199,9 @@ function form_builder_preview($f, &$form_state, $form, $form_type, $form_id) { $form['#attached']['js'][] = 'misc/form.js'; $form['#attached']['js'][] = 'misc/collapse.js'; + $form['#attached']['js'][] = drupal_get_path('module', 'filter') . '/filter.js'; + $form['#attached']['css'][] = drupal_get_path('module', 'filter') . '/filter.css'; + $form['#attached']['js'][] = array('data' => array('machineName' => array()), 'type' => 'setting'); $form['#attached']['js'][] = 'misc/machine-name.js'; @@ -220,17 +223,17 @@ function form_builder_preview($f, &$form_state, $form, $form_type, $form_id) { /** * Form containing all the current weights and parents of elements. */ -function form_builder_positions($form, &$form_state, $preview_form, $form_type, $form_id) { +function form_builder_positions($form, &$form_state, $form_cache, $form_type, $form_id) { $form = array( '#tree' => TRUE, '#form_builder' => array( 'form_type' => $form_type, 'form_id' => $form_id, - 'form' => $preview_form, + 'form' => $form_cache, ), ); - form_builder_positions_prepare($form, $preview_form); + _form_builder_positions_prepare($form, $form_cache); // Drupal MUST have a button to register submissions. // Add a button even though the form is only submitted via AJAX. @@ -245,30 +248,30 @@ function form_builder_positions($form, &$form_state, $preview_form, $form_type, /** * Recursive helper for form_builder_positions(). Add weight fields. */ -function form_builder_positions_prepare(&$form, &$preview_form, $parent_id = FORM_BUILDER_ROOT) { - foreach (element_children($preview_form) as $key) { +function _form_builder_positions_prepare(&$form, $form_cache, $parent_id = FORM_BUILDER_ROOT) { + foreach (element_children($form_cache) as $key) { // Keep record of the current parent ID. $previous_parent_id = $parent_id; - if (isset($preview_form[$key]['#form_builder']['element_id'])) { + if (isset($form_cache[$key]['#form_builder']['element_id'])) { // Set the parent ID for this element. - $preview_form[$key]['#form_builder']['parent_id'] = $parent_id; - $element_id = $preview_form[$key]['#form_builder']['element_id']; + $form_cache[$key]['#form_builder']['parent_id'] = $parent_id; + $element_id = $form_cache[$key]['#form_builder']['element_id']; $parent_id = $element_id; $form[$element_id]['weight'] = array( '#type' => 'hidden', - '#default_value' => isset($preview_form[$key]['#weight']) ? $preview_form[$key]['#weight'] : 0, + '#default_value' => isset($form_cache[$key]['#weight']) ? $form_cache[$key]['#weight'] : 0, '#attributes' => array('class' => array('form-builder-weight form-builder-element-' . $element_id)), ); $form[$element_id]['parent'] = array( '#type' => 'hidden', - '#default_value' => $preview_form[$key]['#form_builder']['parent_id'], + '#default_value' => $form_cache[$key]['#form_builder']['parent_id'], '#attributes' => array('class' => array('form-builder-parent form-builder-element-' . $element_id)), ); } - form_builder_positions_prepare($form, $preview_form[$key], $parent_id); + _form_builder_positions_prepare($form, $form_cache[$key], $parent_id); $parent_id = $previous_parent_id; } } @@ -282,7 +285,7 @@ function form_builder_positions_submit(&$form, &$form_state) { $form_type = $form['#form_builder']['form_type']; $form_id = $form['#form_builder']['form_id']; - $preview_form = $form['#form_builder']['form']; + $form_cache = $form['#form_builder']['form']; foreach (element_children($form) as $element_id) { // Skip items without weight value (like the form token, build_id, etc). @@ -291,14 +294,17 @@ function form_builder_positions_submit(&$form, &$form_state) { } // Check for changed weights or parents. - $element = form_builder_get_element($preview_form, $element_id); + $element = form_builder_get_element($form_cache, $element_id); $element['#weight'] = $form_state['values'][$element_id]['weight']; $element['#form_builder']['parent_id'] = $form_state['values'][$element_id]['parent']; - form_builder_set_element($preview_form, $element); + form_builder_set_element($form_cache, $element); } // Save all the changes made. - form_builder_cache_save($form_type, $form_id, $preview_form); + form_builder_cache_save($form_type, $form_id, $form_cache); + + // Don't redirect, which will cause an unnecessary HTTP request. + $form_state['redirect'] = FALSE; } /** diff --git a/sites/all/modules/form_builder/includes/form_builder.api.inc b/sites/all/modules/form_builder/includes/form_builder.api.inc index b22cb4a2240c6120d9dace19cfb4b0256c41c30c..700808873d2de8c1a222869a1dfced675e4bdbde 100644 --- a/sites/all/modules/form_builder/includes/form_builder.api.inc +++ b/sites/all/modules/form_builder/includes/form_builder.api.inc @@ -278,6 +278,22 @@ function form_builder_get_element_ids($form) { return $element_ids; } +/** + * Recursive function to get the types of all element within a form. + */ +function form_builder_get_element_types($form) { + $element_types = array(); + foreach (element_children($form) as $key) { + if (isset($form[$key]['#form_builder']['element_type'])) { + $element_types[] = $form[$key]['#form_builder']['element_type']; + } + $additional_types = form_builder_get_element_types($form[$key]); + $element_types = array_merge($element_types, $additional_types); + } + + return $element_types; +} + /** * Loader function to retrieve a form builder configuration array. * @@ -333,7 +349,7 @@ function form_builder_add_default_properties($form, $form_type, $key = NULL, $pa else { // If the type cannot be found, prevent editing of this field. unset($form['#form_builder']); - return; + return $form; } } diff --git a/sites/all/modules/form_builder/includes/form_builder.cache.inc b/sites/all/modules/form_builder/includes/form_builder.cache.inc index 5f91cb5a35c0a5172ea8af0deb7d62f4beff47af..2b803759e22ec9084e62a6552918c810c7926abc 100644 --- a/sites/all/modules/form_builder/includes/form_builder.cache.inc +++ b/sites/all/modules/form_builder/includes/form_builder.cache.inc @@ -92,8 +92,6 @@ function form_builder_cache_purge($expire_threshold = NULL) { return db_delete('form_builder_cache') ->condition('updated', REQUEST_TIME - $expire_threshold, '<') - ->condition('type', $form_type) - ->condition('form_id', $form_id) ->execute(); } diff --git a/sites/all/modules/form_builder/includes/form_builder.properties.inc b/sites/all/modules/form_builder/includes/form_builder.properties.inc index 42edc687f4f2a501db3dde14c6f313030c63432b..f8e7391523d40b532dd6f49fbdd7a3d76fcc619b 100644 --- a/sites/all/modules/form_builder/includes/form_builder.properties.inc +++ b/sites/all/modules/form_builder/includes/form_builder.properties.inc @@ -242,7 +242,7 @@ function form_builder_property_default_value_form(&$form_state, $form_type, $ele // exception, though. '#type' => $element['#type'] == 'textarea' ? 'textarea' : 'textfield', '#title' => t('Default value'), - '#default_value' => $element['#default_value'], + '#default_value' => $element['#type'] == 'value' ? $element['#value'] : $element['#default_value'], '#weight' => 1, ); diff --git a/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc b/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc index b487734670526e7080dc21f7d84234a303cc41cc..3466bebc30949c65d51198de82d15001eaa72fd6 100644 --- a/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc +++ b/sites/all/modules/form_builder/modules/webform/form_builder_webform.components.inc @@ -37,13 +37,13 @@ function _form_builder_webform_form_builder_map_date() { 'form_parents' => array('default', 'timezone'), 'storage_parents' => array('extra', 'timezone'), ), - 'year_start' => array( - 'form_parents' => array('validation', 'year_start'), - 'storage_parents' => array('extra', 'year_start'), + 'start_date' => array( + 'form_parents' => array('validation', 'start_date'), + 'storage_parents' => array('extra', 'start_date'), ), - 'year_end' => array( - 'form_parents' => array('validation', 'year_end'), - 'storage_parents' => array('extra', 'year_end'), + 'end_date' => array( + 'form_parents' => array('validation', 'end_date'), + 'storage_parents' => array('extra', 'end_date'), ), 'year_textfield' => array( 'form_parents' => array('display', 'year_textfield'), @@ -204,6 +204,22 @@ function _form_builder_webform_form_builder_map_file() { ); } +/** + * Implements _form_builder_webform_form_builder_load_component(). + */ +function _form_builder_webform_form_builder_load_file($form_element) { + if (isset($form_element['#upload_validators'])) { + // Extension list and size comes from #upload_validators on load. + $form_element['#webform_file_extensions']['types'] = empty($form_element['#upload_validators']['file_validate_extensions'][0]) ? array() : explode(' ', $form_element['#upload_validators']['file_validate_extensions'][0]); + $form_element['#webform_file_size'] = empty($form_element['#upload_validators']['file_validate_size'][0]) ? '' : format_size($form_element['#upload_validators']['file_validate_size'][0]); + // File directory and scheme come from #upload_location on load. + $form_element['#webform_file_directory'] = preg_replace('/^webform[\/]?/', '', file_uri_target($form_element['#upload_location'])); + $form_element['#webform_file_scheme'] = file_uri_scheme($form_element['#upload_location']); + } + + return $form_element; +} + /** * Implements _form_builder_webform_form_builder_preview_alter_component(). */ @@ -384,6 +400,17 @@ function _form_builder_webform_form_builder_types_hidden() { return $fields; } +/** + * Implements _form_builder_webform_form_builder_load_component(). + */ +function _form_builder_webform_form_builder_load_hidden($form_element) { + // Hidden elements may be #type "value" or "hidden". Set the internal property + // to keep track of hidden fields. + $form_element['#form_builder']['element_type'] = 'hidden'; + + return $form_element; +} + /** * Implements _form_builder_webform_form_builder_preview_alter_component(). */ @@ -399,12 +426,7 @@ function _form_builder_webform_form_builder_preview_alter_hidden($form_element) // Display the title of the hidden field as regular markup. $form_element['#children'] = t('@title - <em>hidden field</em>', array('@title' => $form_element['#title'])); - $form_element['#title_display'] = 'none'; - - // Give the element a wrapper class so that themers can recognize it - // represents a hidden element. - $form_element['#attributes']['class'][] = 'form-builder-preview-hidden-webform-element'; - array_unshift($form_element['#theme_wrappers'], 'container'); + $form_element['#title'] = NULL; return $form_element; } @@ -598,12 +620,30 @@ function _form_builder_webform_form_builder_load_pagebreak($form_element) { // Pagebreak components are rendered as hidden elements by webform, but // hidden elements do not have a #title property. So we have to convert the // rendered value to be used as the #title instead. - if ($form_element['#type'] == 'pagebreak') { - $form_element['#title'] = $form_element['#value']; - } + $form_element['#title'] = $form_element['#value']; + $form_element['#form_builder']['element_type'] = 'pagebreak'; + return $form_element; } +/** + * Implements _form_builder_webform_form_builder_save_component(). + */ +function _form_builder_webform_form_builder_save_pagebreak($component, $form_element) { + // Ensure pagebreaks are saved at the root level. + if ($component['pid'] !== 0) { + drupal_set_message(t('Page breaks may not be nested inside fieldsets. Each pagebreak has been moved outside of fieldsets.'), 'status', FALSE); + + $form_cache = form_builder_cache_load('webform', $form_element['#form_builder']['form_id']); + $parent = form_builder_get_element($form_cache, $form_element['#form_builder']['parent_id']); + + $component['weight'] = $parent['#weight'] + 1; + $component['pid'] = 0; + } + + return $component; +} + /** * Implements _form_builder_webform_form_builder_preview_alter_component(). */ @@ -746,7 +786,7 @@ function _form_builder_webform_form_builder_save_select($component, $form_elemen */ function form_builder_webform_property_select_options_form(&$form_state, $form_type, $element) { // Use the default options form, but enhance to allow Webform tokens. - $form = form_builder_property_options_form($form_state, $form_type, $element); + $form = form_builder_property_options_form($form_state, $form_type, $element, 'options'); $form['options']['#default_value_pattern'] = '^%.+\[.+\]$'; return $form; } @@ -885,7 +925,7 @@ function _form_builder_webform_form_builder_map_time() { 'storage_parents' => array('extra', 'timezone'), ), 'hourformat' => array( - 'storage_parents' => array('display', 'hourformat'), + 'form_parents' => array('display', 'hourformat'), 'storage_parents' => array('extra', 'hourformat'), ), ), @@ -916,8 +956,9 @@ function _form_builder_webform_mapped_form(&$form_state, $form_type, $element, $ function _form_builder_webform_save_mapped_component($component, $element) { if ($map = _form_builder_webform_property_map($component['type'])) { foreach ($map['properties'] as $property => $property_map) { - if (isset($element['#' . $property]) && isset($property_map['storage_parents'])) { - _form_builder_webform_save_mapped_component_value($component, $element['#' . $property], $property_map['storage_parents']); + if (isset($property_map['storage_parents'])) { + $property_value = isset($element['#' . $property]) ? $element['#' . $property] : NULL; + _form_builder_webform_save_mapped_component_value($component, $property_value, $property_map['storage_parents']); } } } @@ -967,6 +1008,9 @@ function _form_builder_webform_default($component_type, $merge_extras = array()) // Call the loading function to make sure that the default element gets the // same treatment as an existing one. $default_element = _form_builder_webform_set_mapped_type($default_element); + if ($element = form_builder_webform_component_invoke($component_type, 'form_builder_load', $default_element)) { + $default_element = $element; + } return $default_element; } @@ -1016,11 +1060,12 @@ function _form_builder_webform_build_edit_form($component_type, $element, $prope // component. $defaults_function = '_webform_defaults_' . $component_type; $component = isset($element['#webform_component']) ? $element['#webform_component'] : $defaults_function(); + $nid = isset($component['nid']) ? $component['nid'] : NULL; // The most up-to-date configuration data stored by Form Builder for the // part of the component we are editing is also stored in the passed-in // element, and should always take precedence. - if (isset($element["#$property"])) { + if (array_key_exists("#$property", $element)) { drupal_array_set_nested_value($component, $component_nested_keys, $element["#$property"]); } @@ -1028,7 +1073,10 @@ function _form_builder_webform_build_edit_form($component_type, $element, $prope // the component, and obtain the slice of it that we want. $empty_form = array(); $empty_form_state = form_state_defaults(); - $node = (object) array('nid' => NULL); + + // The full node is needed here so that the "private" option can be access + // checked. + $node = !isset($nid) ? (object) array('nid' => NULL) : node_load($nid); $form = webform_component_edit_form($empty_form, $empty_form_state, $node, $component); $form = drupal_array_get_nested_value($form, $form_nested_keys); diff --git a/sites/all/modules/form_builder/modules/webform/form_builder_webform.info b/sites/all/modules/form_builder/modules/webform/form_builder_webform.info index 3f48fec72ecdfb47099ed7c2f1686d786b46da17..a6856e38a2594157a1bcdf8b34c06eca519a9334 100644 --- a/sites/all/modules/form_builder/modules/webform/form_builder_webform.info +++ b/sites/all/modules/form_builder/modules/webform/form_builder_webform.info @@ -4,9 +4,9 @@ core = 7.x dependencies[] = form_builder dependencies[] = webform -; Information added by drupal.org packaging script on 2012-03-08 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2012-09-27 +version = "7.x-1.3" core = "7.x" project = "form_builder" -datestamp = "1331181344" +datestamp = "1348708781" diff --git a/sites/all/modules/form_builder/modules/webform/form_builder_webform.module b/sites/all/modules/form_builder/modules/webform/form_builder_webform.module index a0d356e2aa41e5af45f960136939790041e6d682..2a7c83856dccb00911a579312486fd2c35967bca 100644 --- a/sites/all/modules/form_builder/modules/webform/form_builder_webform.module +++ b/sites/all/modules/form_builder/modules/webform/form_builder_webform.module @@ -52,11 +52,14 @@ function form_builder_webform_save_form($form, &$form_state, $nid) { '#type' => 'value', '#value' => $nid, ); - $form['save'] = array( + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['save'] = array( '#type' => 'submit', '#value' => t('Save'), ); - $form['cancel'] = array( + $form['actions']['cancel'] = array( '#type' => 'submit', '#value' => t('Cancel'), '#submit' => array('form_builder_webform_cancel'), @@ -81,22 +84,47 @@ function form_builder_webform_save_node($node) { $form_cache = form_builder_cache_load('webform', $node->nid); $element_ids = form_builder_preview_prepare($form_cache, 'webform', $node->nid); - // Save modified or created components. - foreach ($element_ids as $id) { - form_builder_webform_save_component($node, $id, $form_cache); - } - - // Delete components that have been removed. + // Remove components if deleted and calculate the highest in-use CID. + $max_cid = 0; foreach ($node->webform['components'] as $component) { $element_id = 'cid_' . $component['cid']; + $cid = $component['cid']; + + // Max CID is used in the creation of new components, preventing conflicts. + $max_cid = max($max_cid, $cid); + + // Remove components from the $node that have been removed in the UI. if (!in_array($element_id, $element_ids)) { - webform_component_delete($node, $component); + if (isset($node->webform['components'][$cid])) { + unset($node->webform['components'][$cid]); + } + } + } + + // Update any new/updated components in the node record. + foreach ($element_ids as $element_id) { + $component = form_builder_webform_get_component($node, $element_id, $form_cache); + if ($component) { + if (empty($component['cid'])) { + $cid = ++$max_cid; + $component['cid'] = $cid; + // Reassign the component ID to the form so that future elements that + // may depend on this one set their parent ID (pid) properly. + $element = form_builder_get_element($form_cache, $element_id); + $element['#webform_component']['cid'] = $cid; + form_builder_set_element($form_cache, $element); + } + else { + $cid = $component['cid']; + } + $node->webform['components'][$cid] = $component; } } - // Reset the node cache, since $node->webform['components'] has changed. - // @todo: Decide if this belongs in the above Webform API functions instead? - entity_get_controller('node')->resetCache(array($node->nid)); + // Save the node itself to update components and allow other modules to + // respond to any changes. The Form Builder cache is intentionally left in + // place so other modules can check it for changes also. + node_save($node); // Remove the cached form_builder form. form_builder_cache_delete('webform', $node->nid); @@ -105,7 +133,7 @@ function form_builder_webform_save_node($node) { /** * Save the contents of a form component into Webform's database tables. */ -function form_builder_webform_save_component($node, $element_id, $form) { +function form_builder_webform_get_component($node, $element_id, $form) { module_load_include('inc', 'form_builder_webform', 'form_builder_webform.components'); $element = form_builder_get_element($form, $element_id); @@ -140,21 +168,9 @@ function form_builder_webform_save_component($node, $element_id, $form) { $component['pid'] = 0; // Set the parent ID for the component if it is nested inside another component. - // This must be done this way so that children of newly added components get a - // proper pid when the cid of the new component has just been generated. $parent = form_builder_get_element($form, $element['#form_builder']['parent_id']); - if ($parent) { - // If the parent is new, the database must be queried for the cid of the parent. - if (isset($parent['#webform_component']['is_new']) && $parent['#webform_component']['is_new']) { - $results = db_query('SELECT cid, form_key FROM {webform_component} WHERE form_key = :key AND nid = :nid', array(':key' => $element['#form_builder']['parent_id'], ':nid' => $node->nid)); - foreach($results as $result) { - $component['pid'] = $result->cid; - } - } - // If the parent is already stored in the webform, grab its cid value. - elseif (isset($parent['#webform_component']['cid'])) { - $component['pid'] = $parent['#webform_component']['cid']; - } + if ($parent && isset($parent['#webform_component']['cid'])) { + $component['pid'] = $parent['#webform_component']['cid']; } // Set the component's value. If the form element doesn't have a default, @@ -176,12 +192,7 @@ function form_builder_webform_save_component($node, $element_id, $form) { $component = $saved_component; } - if (!isset($component['cid'])) { - webform_component_insert($component); - } - elseif ($component != $node->webform['components'][$component['cid']]) { - webform_component_update($component); - } + return $component; } /** @@ -272,32 +283,11 @@ function form_builder_webform_form_builder_load($form_builder_type, $form_builde $nid = $form_builder_id; $node = node_load($nid); - // Since webform_client_form() has special handling for the 'pagebreak' - // component that we do not want to occur here (here we want to display the - // entire webform on a single page, without page breaks), we temporarily - // change the component to 'pagebreak_clone' and then change it back - // afterwards. See _webform_render_pagebreak_clone(). - // @todo: Replace this with something cleaner, if webform eventually allows - // us to do that. - $pagebreak_component_keys = array(); - foreach ($node->webform['components'] as &$component) { - if ($component['type'] == 'pagebreak') { - $component['type'] = 'pagebreak_clone'; - $pagebreak_component_keys[] = $component['form_key']; - } - } - // Get the unfiltered version of the client form. $form = array(); $form_state = array(); $form = webform_client_form($form, $form_state, $node, array(), TRUE, FALSE); - // Change 'pagebreak' components back, as described above. - foreach ($pagebreak_component_keys as $key) { - $form['submitted'][$key]['#type'] = 'pagebreak'; - $form['submitted'][$key]['#webform_component']['type'] = 'pagebreak'; - } - // Perform final processing of the form, and return it. $form += array('submitted' => array()); form_builder_webform_load_process($form['submitted'], $node); @@ -340,6 +330,7 @@ function form_builder_webform_load_process(&$form, $node, $pid = 0) { */ function form_builder_webform_form_builder_add_element_alter(&$element, $form_type, $form_id) { if ($form_type == 'webform') { + $element['#webform_component']['nid'] = is_numeric($form_id) ? $form_id : NULL; $element['#webform_component']['is_new'] = TRUE; } } @@ -369,7 +360,7 @@ function form_builder_webform_form_builder_preview_alter(&$element, $form_type, } // A #title_display property of 0 (as stored by Webform) means no setting. - if (isset($element['#title_display']) && $element['#title_display'] == 0) { + if (isset($element['#title_display']) && strcmp('0', $element['#title_display']) === 0) { unset($element['#title_display']); } } @@ -454,20 +445,6 @@ function form_builder_webform_component_invoke($component_type, $callback) { } } -/** - * Implements _webform_render_component(). - * - * This "component" is only used as a temporary replacement for the standard - * pagebreak component when the form builder version of the webform is being - * rendered; this occurs when form_builder_webform_form_builder_load() calls - * webform_client_form(). Therefore, we do not need to register it as a regular - * webform component anywhere else (and don't want to, since we don't want it - * to be available elsewhere in the webform UI). - */ -function _webform_render_pagebreak_clone($component, $value = NULL, $filter = TRUE) { - return webform_component_invoke('pagebreak', 'render', $component, $value, $filter); -} - /** * Helper function; Retrieve a component's map and merge in generic properties. */ @@ -501,7 +478,7 @@ function _form_builder_webform_property_map($component_type) { } if (webform_component_feature($component_type, 'private')) { - $map['properties']['private'] = array( + $map['properties']['webform_private'] = array( 'form_parents' => array('display', 'private'), 'storage_parents' => array('extra', 'private'), ); @@ -514,6 +491,8 @@ function _form_builder_webform_property_map($component_type) { // All components support the key property. $map['properties']['key'] = array(); + drupal_alter('form_builder_webform_property_map', $map, $component_type); + $maps[$component_type] = $map; } diff --git a/sites/all/modules/imce/imce.info b/sites/all/modules/imce/imce.info index 6394ad4e73a3918942fc9aa1231ea54733a832f2..60b6ac8cf3a3e21d753740190a340d87bea65525 100644 --- a/sites/all/modules/imce/imce.info +++ b/sites/all/modules/imce/imce.info @@ -4,9 +4,9 @@ core = "7.x" package = "Media" configure = "admin/config/media/imce" -; Information added by drupal.org packaging script on 2011-10-20 -version = "7.x-1.5" +; Information added by drupal.org packaging script on 2013-01-29 +version = "7.x-1.7" core = "7.x" project = "imce" -datestamp = "1319104232" +datestamp = "1359476607" diff --git a/sites/all/modules/imce/imce.install b/sites/all/modules/imce/imce.install index 8d3e420199b77a47da67e786042c9a7c081b3118..688c6b5b137e52b390afa0ca58cd3f3518402517 100644 --- a/sites/all/modules/imce/imce.install +++ b/sites/all/modules/imce/imce.install @@ -11,13 +11,13 @@ function imce_install() { module_load_include('inc', 'imce', 'inc/imce.core.profiles'); imce_install_profiles(); - drupal_set_message(st('!module has been installed.', array('!module' => l(st('IMCE'), 'admin/config/media/imce')))); } /** * Implements hook_uninstall(). */ function imce_uninstall() { + db_delete('file_usage')->condition('module', 'imce')->execute(); variable_del('imce_profiles'); variable_del('imce_roles_profiles'); variable_del('imce_settings_textarea'); diff --git a/sites/all/modules/imce/imce.module b/sites/all/modules/imce/imce.module index fe93a1204a4b0cef71cd755bec7ee1489705f870..f3dc4bf79dc3755c53a252de3f629f878e6f35d6 100644 --- a/sites/all/modules/imce/imce.module +++ b/sites/all/modules/imce/imce.module @@ -15,6 +15,7 @@ function imce_menu() { 'title' => 'File browser', 'page callback' => 'imce', 'access callback' => 'imce_access', + 'access arguments' => array(FALSE, 1), 'file' => 'inc/imce.page.inc', 'type' => MENU_CALLBACK, ); @@ -118,7 +119,7 @@ function imce_textarea($element) { if (!isset($regexp)) { $regexp = FALSE; if (imce_access() && $regexp = str_replace(' ', '', variable_get('imce_settings_textarea', ''))) { - $regexp = '@^' . str_replace(',', '|', implode('.*', array_map('preg_quote', explode('*', $regexp)))) . '$@'; + $regexp = '@^(' . str_replace(',', '|', implode('.*', array_map('preg_quote', explode('*', $regexp)))) . ')$@'; } } if ($regexp && preg_match($regexp, $element['#id'])) { @@ -132,58 +133,39 @@ function imce_textarea($element) { * Returns the configuration profile assigned to a user for a specific file scheme. */ function imce_user_profile($user, $scheme = NULL) { - $profiles = variable_get('imce_profiles', array()); - $swrappers = file_get_stream_wrappers(); - $default_scheme = variable_get('file_default_scheme', 'public'); + static $ups = array(); - //handle user#1 separately - if ($user->uid == 1) { - $scheme = empty($scheme) ? $default_scheme : $scheme; - if (isset($profiles[1]) && isset($swrappers[$scheme])) { - return $profiles[1] + array('scheme' => $scheme); - } - return FALSE; + // Set scheme + if (empty($scheme)) { + $scheme = variable_get('file_default_scheme', 'public'); } - //handle regular users - $roles_profiles = variable_get('imce_roles_profiles', array()); - //store assigned configuration - $conf = array(); - foreach ($roles_profiles as $rid => $role) { - if (isset($user->roles[$rid])) { - $conf = $role; - break; - } + // Return from cache. + if (isset($ups[$scheme][$user->uid])) { + return $ups[$scheme][$user->uid]; } + $ups[$scheme][$user->uid] = FALSE; - //no scheme-profile assignment - if (empty($conf)) { + // Check scheme + $swrappers = file_get_stream_wrappers(); + if (!isset($swrappers[$scheme])) { return FALSE; } - //return the profile for the specified scheme - if (!empty($scheme)) { - $key = $scheme . '_pid'; - if (isset($conf[$key]) && isset($profiles[$conf[$key]]) && isset($swrappers[$scheme])) { - return $profiles[$conf[$key]] + array('scheme' => $scheme); - } - return FALSE; - } + $profiles = variable_get('imce_profiles', array()); + $scinfo = array('scheme' => $scheme); - //no scheme specified. check the default - $scheme = $default_scheme; - $key = $scheme . '_pid'; - if (isset($conf[$key]) && isset($profiles[$conf[$key]]) && isset($swrappers[$scheme])) { - return $profiles[$conf[$key]] + array('scheme' => $scheme); + // Handle user#1 separately + if ($user->uid == 1) { + return $ups[$scheme][$user->uid] = isset($profiles[1]) ? $profiles[1] + $scinfo : FALSE; } - //check if any of the schemes has a profile assigned. - foreach ($conf as $key => $pid) { - if (substr($key, -4) == '_pid' && isset($profiles[$pid])) { - $scheme = substr($key, 0, -4); - if (isset($swrappers[$scheme])) { - return $profiles[$pid] + array('scheme' => $scheme); - } + // Handle regular users. + $roles_profiles = variable_get('imce_roles_profiles', array()); + $sckey = $scheme . '_pid'; + foreach ($roles_profiles as $rid => $conf) { + if (isset($user->roles[$rid]) && isset($conf[$sckey]) && isset($profiles[$conf[$sckey]])) { + return $ups[$scheme][$user->uid] = $profiles[$conf[$sckey]] + $scinfo; } } @@ -194,28 +176,11 @@ function imce_user_profile($user, $scheme = NULL) { * Checks if the user is assigned an imce profile. * A more detailed assignment check is performed before imce loads. */ -function imce_access($user = FALSE) { +function imce_access($user = FALSE, $scheme = NULL) { if ($user === FALSE) { global $user; } - - if ($user->uid == 1) { - return TRUE; - } - - $roles_profiles = variable_get('imce_roles_profiles', array()); - foreach ($roles_profiles as $rid => $role) { - if (isset($user->roles[$rid])) { - foreach ($role as $key => $pid) { - if (substr($key, -4) == '_pid' && $pid) { - return TRUE; - } - } - break; - } - } - - return FALSE; + return imce_user_profile($user, $scheme) ? TRUE : FALSE; } /** @@ -225,7 +190,6 @@ function imce_user_page_access($account, $user = FALSE) { if ($user === FALSE) { global $user; } - return ($user->uid == 1 || $account->uid == $user->uid) && ($profile = imce_user_profile($account)) && $profile['usertab']; } @@ -233,5 +197,5 @@ function imce_user_page_access($account, $user = FALSE) { * Check if the directory name is regular. */ function imce_reg_dir($dirname) { - return $dirname == '.' || (is_string($dirname) && $dirname != '' && !preg_match('@(^\s)|(^/)|(^\./)|(\s$)|(/$)|(/\.$)|(\.\.)|(//)|(\\\\)|(/\./)@', $dirname)); + return $dirname == '.' || is_int($dirname) || (is_string($dirname) && $dirname != '' && !preg_match('@(^\s)|(^/)|(^\./)|(\s$)|(/$)|(/\.$)|(\.\.)|(//)|(\\\\)|(/\./)@', $dirname)); } \ No newline at end of file diff --git a/sites/all/modules/imce/inc/imce.admin.inc b/sites/all/modules/imce/inc/imce.admin.inc index 4ff3077bb9c9ebe1ac21cab47327877f6a00f9a9..ff4961836ff255986b5398cf4e5a4f03779c9597 100644 --- a/sites/all/modules/imce/inc/imce.admin.inc +++ b/sites/all/modules/imce/inc/imce.admin.inc @@ -249,7 +249,7 @@ function imce_profile_form($form, &$form_state, $pid = 0) { ); $form['dimensions'] = array( '#type' => 'textfield', - '#title' => t('Maximum image resolution'), + '#title' => t('Maximum image dimensions'), '#default_value' => $profile['dimensions'], '#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction. If an <a href="!image-toolkit-link">image toolkit</a> is installed, files exceeding this value will be scaled down to fit.', array('!image-toolkit-link' => url('admin/config/media/image-toolkit'))), '#field_suffix' => '<kbd>' . t('WIDTHxHEIGHT') . '</kbd>', diff --git a/sites/all/modules/imce/inc/imce.page.inc b/sites/all/modules/imce/inc/imce.page.inc index 27b27adaa7ca088f96bb60bdbcaa4b08fe0d87dc..bcb00b2d2e6eb7a43b48329877d59fa31929ff71 100644 --- a/sites/all/modules/imce/inc/imce.page.inc +++ b/sites/all/modules/imce/inc/imce.page.inc @@ -11,6 +11,7 @@ function imce($scheme = NULL) { module_invoke('admin_menu', 'suppress');//suppress admin_menu $jsop = isset($_GET['jsop']) ? $_GET['jsop'] : NULL; + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); print imce_page($GLOBALS['user'], $scheme, $jsop); exit(); } @@ -258,7 +259,7 @@ function imce_fileop_form_validate($form, &$form_state) { return form_error($form['filenames'], t('Please select a file.')); } - //filenames come seperated by colon + //filenames come separated by colon $filenames = explode(':', $form_state['values']['filenames']); $cnt = count($filenames); //check the number of files. @@ -359,7 +360,7 @@ function imce_resize_submit($form, &$form_state) { //check dimensions $width = (int) $form_state['values']['width']; $height = (int) $form_state['values']['height']; - list($maxw, $maxh) = explode('x', $imce['dimensions']); + list($maxw, $maxh) = $imce['dimensions'] ? explode('x', $imce['dimensions']) : array(0, 0); if ($width < 1 || $height < 1 || ($maxw && ($width > $maxw || $height > $maxh))) { drupal_set_message(t('Please specify dimensions within the allowed range that is from 1x1 to @dimensions.', array('@dimensions' => $imce['dimensions'] ? $imce['dimensions'] : t('unlimited'))), 'error'); return; @@ -424,10 +425,6 @@ function imce_delete_filepath($uri) { if (!file_delete($file, TRUE)) { return FALSE; } - // Remove imce usage - if ($is_imce) { - file_usage_delete($file, 'imce'); - } } // Not in db. Probably loaded via ftp. elseif (!file_unmanaged_delete($uri)) { @@ -676,13 +673,25 @@ function imce_validate_quotas($file, &$imce, $add = 0) { } /** - * Check if the file is an image and return info. + * Checks if the file is an image and returns info. + * There are two switchable versions that use image_get_info() and getimagesize() */ -function imce_image_info($file) { - if (is_file($file) && ($dot = strrpos($file, '.')) && in_array(strtolower(substr($file, $dot+1)), array('jpg', 'jpeg', 'gif', 'png')) && ($info = @getimagesize($file)) && in_array($info[2], array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG)) ) { - return array('width' => $info[0], 'height' => $info[1], 'type' => $info[2], 'mime' => $info['mime']); +if (variable_get('imce_image_get_info', 0)) { + function imce_image_info($file) { + $mimes = array('image/jpeg' => IMAGETYPE_JPEG, 'image/gif' => IMAGETYPE_GIF, 'image/png' => IMAGETYPE_PNG); + if (is_file($file) && ($dot = strrpos($file, '.')) && in_array(strtolower(substr($file, $dot+1)), array('jpg', 'jpeg', 'gif', 'png')) && ($info = @image_get_info($file)) && isset($mimes[$info['mime_type']]) ) { + return array('width' => $info['width'], 'height' => $info['height'], 'type' => $mimes[$info['mime_type']], 'mime' => $info['mime_type']); + } + return FALSE; + } +} +else { + function imce_image_info($file) { + if (is_file($file) && ($dot = strrpos($file, '.')) && in_array(strtolower(substr($file, $dot+1)), array('jpg', 'jpeg', 'gif', 'png')) && ($info = @getimagesize($file)) && in_array($info[2], array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG)) ) { + return array('width' => $info[0], 'height' => $info[1], 'type' => $info[2], 'mime' => $info['mime']); + } + return FALSE; } - return FALSE; } /** @@ -838,6 +847,7 @@ function imce_working_directory(&$imce) { //or the whole list. foreach ($imce['directories'] as $dirname => $info) { + $dirname = (string) $dirname; if (imce_check_directory($dirname, $imce)) { if ($sess) { $_SESSION['imce_directory'] = rawurlencode($dirname); diff --git a/sites/all/modules/imce/js/imce.js b/sites/all/modules/imce/js/imce.js index 2a2f9b4011c74df0eddae5583effeaf7e5d3993e..f0380873375d20c531891217ef81059065c90747 100644 --- a/sites/all/modules/imce/js/imce.js +++ b/sites/all/modules/imce/js/imce.js @@ -424,7 +424,7 @@ navCache: function (dir, newdir) { //validate upload form uploadValidate: function (data, form, options) { - var path = data[0].value; + var path = $('#edit-imce').val(); if (!path) return false; if (imce.conf.extensions != '*') { var ext = path.substr(path.lastIndexOf('.') + 1); @@ -492,7 +492,7 @@ fopSettings: function (fop) { //toggle loading state fopLoading: function(fop, state) { - var el = imce.el('edit-'+ fop), func = state ? 'addClass' : 'removeClass' + var el = imce.el('edit-'+ fop), func = state ? 'addClass' : 'removeClass'; if (el) { $(el)[func]('loading').attr('disabled', state); } diff --git a/sites/all/modules/imce/js/imce_extras.js b/sites/all/modules/imce/js/imce_extras.js index 349de2d1edc48d94be6d3204246c907aec100ef5..bccf1c1d3ff2313072dec05dda9556a27efffdc4 100644 --- a/sites/all/modules/imce/js/imce_extras.js +++ b/sites/all/modules/imce/js/imce_extras.js @@ -117,15 +117,10 @@ imce.firstSort = function() { //sort file list according to column index. imce.columnSort = function(cid, dsc) { if (imce.findex.length < 2) return; - if (cid == imce.vars.cid && dsc != imce.vars.dsc) { - imce.findex.reverse(); - } - else { - var func = 'sort'+ (cid == 0 ? 'Str' : 'Num') + (dsc ? 'Dsc' : 'Asc'); - var prop = cid == 2 || cid == 3 ? 'innerHTML' : 'id'; - //sort rows - imce.findex.sort(cid ? function(r1, r2) {return imce[func](r1.cells[cid][prop], r2.cells[cid][prop])} : function(r1, r2) {return imce[func](r1.id, r2.id)}); - } + var func = 'sort'+ (cid == 0 ? 'Str' : 'Num') + (dsc ? 'Dsc' : 'Asc'); + var prop = cid == 2 || cid == 3 ? 'innerHTML' : 'id'; + //sort rows + imce.findex.sort(cid ? function(r1, r2) {return imce[func](r1.cells[cid][prop], r2.cells[cid][prop])} : function(r1, r2) {return imce[func](r1.id, r2.id)}); //insert sorted rows for (var row, i=0; row = imce.findex[i]; i++) { imce.tbody.appendChild(row); diff --git a/sites/all/modules/link/link.css b/sites/all/modules/link/link.css index 7bdec9e18a560a2c7384c1eb377b158c36066d71..1590e7ad51bc27bf461157af85bcfabd6846d0b7 100644 --- a/sites/all/modules/link/link.css +++ b/sites/all/modules/link/link.css @@ -1,8 +1,8 @@ -div.link-field-column { +.link-field-column { float: left; width: 48%; } -div.link-field-column .form-text { +.link-field-column .form-text { width: 95%; } diff --git a/sites/all/modules/link/link.devel_generate.inc b/sites/all/modules/link/link.devel_generate.inc new file mode 100644 index 0000000000000000000000000000000000000000..7be4a0dde2eb763f2e48c08410861783e4463a9f --- /dev/null +++ b/sites/all/modules/link/link.devel_generate.inc @@ -0,0 +1,29 @@ +<?php + +/** + * @file + * Devel Generate support for Link module. + */ + +/** + * Implements hook_devel_generate(). + */ +function link_devel_generate($object, $field, $instance, $bundle) { + if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) { + return devel_generate_multiple('_link_devel_generate', $object, $field, $instance, $bundle); + } + else { + return _link_devel_generate($object, $field, $instance, $bundle); + } +} + +/** + * Callback for hook_devel_generate(). + */ +function _link_devel_generate($object, $field, $instance, $bundle) { + return array( + 'url' => url('<front>', array('absolute' => TRUE)), + 'title' => devel_create_greeking(mt_rand(1, 3), TRUE), + 'attributes' => _link_default_attributes(), + ); +} diff --git a/sites/all/modules/link/link.diff.inc b/sites/all/modules/link/link.diff.inc new file mode 100644 index 0000000000000000000000000000000000000000..9e34123ae4d9fd4324f60fe2919af89c4299bf58 --- /dev/null +++ b/sites/all/modules/link/link.diff.inc @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Provide diff field functions for the Link module. + */ + +/** + * Diff field callback for parsing link fields comparative values. + */ +function link_field_diff_view($items, $context) { + $diff_items = array(); + foreach ($items as $delta => $item) { + if ($item['url'] && $item['title']) { + $diff_items[$delta] = $item['title'] . ' (' . $item['url'] . ')'; + } + else { + $diff_items[$delta] = $item['title'] . $item['url']; + } + } + return $diff_items; +} diff --git a/sites/all/modules/link/link.info b/sites/all/modules/link/link.info index 8a5d0c18ccfbddac667cf29f77072cc5a5eb6aec..57aedb3983ded3bd22e46062992a68aa2273132c 100644 --- a/sites/all/modules/link/link.info +++ b/sites/all/modules/link/link.info @@ -4,7 +4,8 @@ core = 7.x package = Fields files[] = link.module -files[] = link.install +files[] = link.migrate.inc + ; Tests files[] = tests/link.test files[] = tests/link.attribute.test @@ -17,9 +18,9 @@ files[] = tests/link.validate.test files[] = views/link_views_handler_argument_target.inc files[] = views/link_views_handler_filter_protocol.inc -; Information added by drupal.org packaging script on 2011-10-23 -version = "7.x-1.0" +; Information added by drupal.org packaging script on 2013-02-09 +version = "7.x-1.1" core = "7.x" project = "link" -datestamp = "1319392535" +datestamp = "1360444361" diff --git a/sites/all/modules/link/link.install b/sites/all/modules/link/link.install index fdfc5d1e2da4ab883317d9fd25a2a1b42be2cbc9..14e745d42158981b34657879dcb846f66f83e62c 100644 --- a/sites/all/modules/link/link.install +++ b/sites/all/modules/link/link.install @@ -18,20 +18,21 @@ function link_field_schema($field) { 'columns' => array( 'url' => array( 'type' => 'varchar', - 'length' => 2048, // Maximum URLs length. + // Maximum URLs length. + 'length' => 2048, 'not null' => FALSE, - 'sortable' => TRUE + 'sortable' => TRUE, ), 'title' => array( 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, - 'sortable' => TRUE + 'sortable' => TRUE, ), 'attributes' => array( 'type' => 'text', 'size' => 'medium', - 'not null' => FALSE + 'not null' => FALSE, ), ), ); @@ -87,8 +88,7 @@ function link_update_7000() { * Renames all displays from foobar to link_foobar */ function link_update_7001() { - // for each field that is a link field, we need to update the display types: - + // Update the display type for each link field type. $result = db_query("SELECT id, field_name, data FROM {field_config} WHERE module = 'link' AND type = 'link_field'"); foreach ($result as $field) { $field_id = $field->id; @@ -102,13 +102,12 @@ function link_update_7001() { $update_instance = FALSE; foreach ($instance_data['display'] as $display_name => $display_data) { if ($display_data['type'] && (0 !== strpos($display_data['type'], 'link_'))) { - $instance_data['display'][$display_name]['type'] = 'link_'. $display_data['type']; + $instance_data['display'][$display_name]['type'] = 'link_' . $display_data['type']; $update_instance = TRUE; } } if ($update_instance) { - // update the database. - $num_updated = db_update('field_config_instance') + db_update('field_config_instance') ->fields(array('data' => serialize($instance_data))) ->condition('id', $instance->id) ->execute(); diff --git a/sites/all/modules/link/link.migrate.inc b/sites/all/modules/link/link.migrate.inc new file mode 100644 index 0000000000000000000000000000000000000000..6388d11e44aa0f7429b7cf34520de7c4bea48796 --- /dev/null +++ b/sites/all/modules/link/link.migrate.inc @@ -0,0 +1,108 @@ +<?php + +/** + * @file + * Support for migrate module. + * + * With Migrate 2.4 or later, you can use the subfield syntax to set the title + * and attributes: + * + * @code + * $this->addFieldMapping('field_my_link', 'source_url'); + * $this->addFieldMapping('field_my_link:title', 'source_title'); + * $this->addFieldMapping('field_my_link:attributes', 'source_attributes'); + * @endcode + * + * With earlier versions of Migrate, you must pass an arguments array: + * + * @code + * $link_args = array( + * 'title' => array('source_field' => 'source_title'), + * 'attributes' => array('source_field' => 'source_attributes'), + * ); + * $this->addFieldMapping('field_my_link', 'source_url') + * ->arguments($link_args); + * @endcode + */ + +/** + * Implements hook_migrate_api(). + */ +function link_migrate_api() { + return array( + 'api' => 2, + 'field handlers' => array('MigrateLinkFieldHandler'), + ); +} + +class MigrateLinkFieldHandler extends MigrateFieldHandler { + public function __construct() { + $this->registerTypes(array('link_field')); + } + + static function arguments($title = NULL, $attributes = NULL, $language = NULL) { + $arguments = array(); + if (!is_null($title)) { + $arguments['title'] = $title; + } + if (!is_null($attributes)) { + $arguments['attributes'] = $attributes; + } + if (!is_null($language)) { + $arguments['language'] = $language; + } + return $arguments; + } + + /** + * Implementation of MigrateFieldHandler::fields(). + * + * @param $type + * The field type. + * @param $instance + * Instance info for the field. + * @param Migration $migration + * The migration context for the parent field. We can look at the mappings + * and determine which subfields are relevant. + * @return array + */ + public function fields($type, $instance, $migration = NULL) { + return array( + 'title' => t('Subfield: The link title attribute'), + 'attributes' => t('Subfield: The attributes for this link'), + 'language' => t('Subfield: The language for the field'), + ); + } + + public function prepare($entity, array $field_info, array $instance, array $values) { + if (isset($values['arguments'])) { + $arguments = $values['arguments']; + unset($values['arguments']); + } + else { + $arguments = array(); + } + + $language = $this->getFieldLanguage($entity, $field_info, $arguments); + $values = array_filter($values); + + foreach ($values as $delta => $value) { + $item = array(); + if (isset($arguments['title'])) { + if (!is_array($arguments['title'])) { + $item['title'] = $arguments['title']; + } + elseif (isset($arguments['title'][$delta])) { + $item['title'] = $arguments['title'][$delta]; + } + } + if (isset($arguments['attributes'])) { + $item['attributes'] = $arguments['attributes']; + } + $item['url'] = $value; + $return[$language][$delta] = $item; + } + + return isset($return) ? $return : NULL; + } +} diff --git a/sites/all/modules/link/link.module b/sites/all/modules/link/link.module index c7af1ffbfbe667e3647581ec3b299a511ace9780..241066d55c7c1797e5908099318735e0f62b507e 100644 --- a/sites/all/modules/link/link.module +++ b/sites/all/modules/link/link.module @@ -35,7 +35,7 @@ function link_field_info() { 'url' => 0, 'title' => 'optional', 'title_value' => '', - 'title_maxlength' => 128, //patch #1307788 from nmc + 'title_maxlength' => 128, 'enable_tokens' => 1, 'display' => array( 'url_cutoff' => 80, @@ -46,7 +46,7 @@ function link_field_info() { 'url' => 0, 'title' => 'optional', 'title_value' => '', - 'title_maxlength' => 128, // patch #1307788 from nmc + 'title_maxlength' => 128, 'enable_tokens' => 1, 'display' => array( 'url_cutoff' => 80, @@ -69,7 +69,7 @@ function link_field_instance_settings_form($field, $instance) { $form = array( '#element_validate' => array('link_field_settings_form_validate'), ); - + $form['validate_url'] = array( '#type' => 'checkbox', '#title' => t('Validate URL'), @@ -97,7 +97,7 @@ function link_field_instance_settings_form($field, $instance) { '#title' => t('Link Title'), '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : 'optional', '#options' => $title_options, - '#description' => t('If the link title is optional or required, a field will be displayed to the end user. If the link title is static, the link will always use the same title. If <a href="http://drupal.org/project/token">token module</a> is installed, the static title value may use any other node field as its value. Static and token-based titles may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'), + '#description' => t('If the link title is optional or required, a field will be displayed to the end user. If the link title is static, the link will always use the same title. If <a href="http://drupal.org/project/token">token module</a> is installed, the static title value may use any other entity field as its value. Static and token-based titles may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'), ); $form['title_value'] = array( @@ -107,14 +107,14 @@ function link_field_instance_settings_form($field, $instance) { '#description' => t('This title will always be used if “Static Title” is selected above.'), ); - $form['title_maxlength'] = array( // patch #1307788 from nmc + $form['title_maxlength'] = array( '#type' => 'textfield', '#title' => t('Max length of title field'), '#default_value' => isset($instance['settings']['title_maxlength']) ? $instance['settings']['title_maxlength'] : '128', '#description' => t('Set a maximum length on the title field (applies only if Link Title is optional or required). The maximum limit is 255 characters.'), '#maxlength' => 3, '#size' => 3, - ); + ); if (module_exists('token')) { // Add token module replacements fields @@ -125,23 +125,19 @@ function link_field_instance_settings_form($field, $instance) { '#title' => t('Placeholder tokens'), '#description' => t("The following placeholder tokens can be used in both paths and titles. When used in a path or title, they will be replaced with the appropriate values."), ); - $token_type = array( - 'theme' => 'token_tree', - 'token_types' => array($instance['entity_type']), - 'global_types' => TRUE, - 'click_insert' => TRUE, - 'recursion_limit' => 2, - ); + $entity_info = entity_get_info($instance['entity_type']); $form['tokens']['help'] = array( - '#type' => 'markup', - '#markup' => theme('token_tree', $token_type), + '#theme' => 'token_tree', + '#token_types' => array($entity_info['token type']), + '#global_types' => TRUE, + '#click_insert' => TRUE, ); $form['enable_tokens'] = array( '#type' => 'checkbox', '#title' => t('Allow user-entered tokens'), '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1, - '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the node edit form. This does not affect the field settings on this page.'), + '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the entity edit form. This does not affect the field settings on this page.'), ); } @@ -154,7 +150,7 @@ function link_field_instance_settings_form($field, $instance) { '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80', '#description' => t('If the user does not include a title for this link, the URL will be used as the title. When should the link title be trimmed and finished with an elipsis (…)? Leave blank for no limit.'), '#maxlength' => 3, - '#size' => 3, + '#size' => 3, ); $target_options = array( @@ -181,6 +177,18 @@ function link_field_instance_settings_form($field, $instance) { '#field_suffix' => '"', '#size' => 20, ); + $rel_remove_options = array( + 'default' => t('Keep rel as set up above (untouched/default)'), + 'rel_remove_external' => t('Remove rel if given link is external'), + 'rel_remove_internal' => t('Remove rel if given link is internal'), + ); + $form['rel_remove'] = array( + '#type' => 'radios', + '#title' => t('Remove rel attribute automatically'), + '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'], + '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'), + '#options' => $rel_remove_options, + ); $form['attributes']['class'] = array( '#type' => 'textfield', '#title' => t('Additional CSS Class'), @@ -205,29 +213,28 @@ function link_field_instance_settings_form($field, $instance) { } /** - * Validate the field settings form. + * #element_validate handler for link_field_instance_settings_form(). */ function link_field_settings_form_validate($element, &$form_state, $complete_form) { - if ($form_state['values']['instance']['settings']['title'] === 'value' - && empty($form_state['values']['instance']['settings']['title_value'])) { + if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) { form_set_error('title_value', t('A default title must be provided if the title is a static value.')); } - if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) // patch #1307788 from nmc - && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) { + if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) { form_set_error('display', t('URL Display Cutoff value must be numeric.')); } - if (empty($form_state['values']['instance']['settings']['title_maxlength'])) { // patch #1307788 from nmc + if (empty($form_state['values']['instance']['settings']['title_maxlength'])) { form_set_value($element['title_maxlength'], '128', $form_state); - } elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) { + } + elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) { form_set_error('title_maxlength', t('The max length of the link title must be numeric.')); - } elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) { + } + elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) { form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.')); } - } /** - * Implement hook_field_is_empty(). + * Implements hook_field_is_empty(). */ function link_field_is_empty($item, $field) { return empty($item['title']) && empty($item['url']); @@ -251,19 +258,28 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode $optional_field_found = FALSE; if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) { foreach ($items as $delta => $value) { - _link_validate($items[$delta], $delta, $field, $entity, $instance, $optional_field_found); + _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found); } } if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) { - form_set_error($field['field_name'] .'][0][title', t('At least one title or URL must be entered.')); + form_set_error($field['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.')); + } +} + +/** + * Implements hook_field_insert(). + */ +function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { + foreach ($items as $delta => $value) { + _link_process($items[$delta], $delta, $field, $entity); } } /** - * Implements hook_field_presave(). + * Implements hook_field_update(). */ -function link_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { +function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $value) { _link_process($items[$delta], $delta, $field, $entity); } @@ -308,13 +324,13 @@ function link_field_widget_form(&$form, &$form_state, $field, $instance, $langco * Unpacks the item attributes for use. */ function _link_load($field, $item, $instance) { - /*return $item['attributes'] = isset($item['attributes']) ? - unserialize($item['attributes']) : - $instance['settings']['attributes'];*/ if (isset($item['attributes'])) { - return unserialize($item['attributes']); + if (!is_array($item['attributes'])) { + $item['attributes'] = unserialize($item['attributes']); + } + return $item['attributes']; } - else if (isset($instance['settings']['attributes'])) { + elseif (isset($instance['settings']['attributes'])) { return $instance['settings']['attributes']; } else { @@ -329,7 +345,8 @@ function _link_process(&$item, $delta = 0, $field, $entity) { // Trim whitespace from URL. $item['url'] = trim($item['url']); - // if no attributes are set then make sure $item['attributes'] is an empty array - this lets $field['attributes'] override it. + // If no attributes are set then make sure $item['attributes'] is an empty + // array, so $field['attributes'] can override it. if (empty($item['attributes'])) { $item['attributes'] = array(); } @@ -340,8 +357,7 @@ function _link_process(&$item, $delta = 0, $field, $entity) { } // Don't save an invalid default value (e.g. 'http://'). - if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) - && is_object($node)) { + if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) { if (!link_validate_url($item['url'])) { unset($item['url']); } @@ -351,45 +367,33 @@ function _link_process(&$item, $delta = 0, $field, $entity) { /** * Validates that the link field has been entered properly. */ -function _link_validate(&$item, $delta, $field, $node, $instance, &$optional_field_found) { - if ($item['url'] - && !(isset($instance['default_value'][$delta]['url']) - && $item['url'] === $instance['default_value'][$delta]['url'] - && !$instance['required'])) { +function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found) { + if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) { // Validate the link. if (link_validate_url(trim($item['url'])) == FALSE) { - form_set_error($field['field_name'] .']['. $delta .'][url', t('Not a valid URL.')); + form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][url', t('The value provided for %field is not a valid URL.', array('%field' => $instance['label']))); } // Require a title for the link if necessary. if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) { - form_set_error($field['field_name'] .']['. $delta .'][title', t('Titles are required for all links.')); + form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][title', t('Titles are required for all links.')); } } // Require a link if we have a title. - if ($instance['settings']['url'] !== 'optional' - && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 - && strlen(trim($item['url'])) == 0) { - form_set_error($field['field_name'] .']['. $delta .'][url', t('You cannot enter a title without a link url.')); + if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) { + form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][url', t('You cannot enter a title without a link url.')); } // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link. - if ($instance['settings']['url'] === 'optional' - && $instance['settings']['title'] === 'optional' - && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) { + if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) { $optional_field_found = TRUE; } // Require entire field - if ($instance['settings']['url'] === 'optional' - && $instance['settings']['title'] === 'optional' - && $instance['required'] == 1 - && !$optional_field_found - && isset($instance['id'])) { - form_set_error($instance['field_name'] .'][0][title', - t('At least one title or URL must be entered.')); + if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) { + form_set_error($instance['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.')); } } /** - * Cleanup user-entered values for a link field according to field settings. + * Clean up user-entered values for a link field according to field settings. * * @param $item * A single link item, usually containing url, title, and attributes. @@ -397,67 +401,88 @@ function _link_validate(&$item, $delta, $field, $node, $instance, &$optional_fie * The delta value if this field is one of multiple fields. * @param $field * The CCK field definition. - * @param $node - * The node containing this link. + * @param $entity + * The entity containing this link. */ -function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { +function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { // Don't try to process empty links. if (empty($item['url']) && empty($item['title'])) { return; } // Replace URL tokens. + $entity_type = $instance['entity_type']; + $entity_info = entity_get_info($entity_type); + $property_id = $entity_info['entity keys']['id']; + $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : ( + $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type + ); if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) { global $user; - // Load the node if necessary for nodes in views. - $token_node = isset($node->nid) ? node_load($node->nid) : $node; - $item['url'] = token_replace($item['url'], array('node' => $token_node)); + // Load the entity if necessary for entities in views. + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded)); } $type = link_validate_url($item['url']); - // If we can't determine the type of url, and we've been told not to validate it, - // then we assume it's a LINK_EXTERNAL type for later processing. #357604 + // If the type of the URL cannot be determined and URL validation is disabled, + // then assume LINK_EXTERNAL for later processing. if ($type == FALSE && $instance['settings']['validate_url'] === 0) { $type = LINK_EXTERNAL; } $url = link_cleanup_url($item['url']); + $url_parts = _link_parse_url($url); - // Separate out the anchor if any. - if (strpos($url, '#') !== FALSE) { - $item['fragment'] = substr($url, strpos($url, '#') + 1); - $url = substr($url, 0, strpos($url, '#')); - } - // Separate out the query string if any. - if (strpos($url, '?') !== FALSE) { - $query = substr($url, strpos($url, '?') + 1); - parse_str($query, $query_array); - $item['query'] = $query_array; - $url = substr($url, 0, strpos($url, '?')); + // We can't check_plain('<front>') because it'll break. + if ($type != LINK_FRONT) { + $url_parts['url'] = check_plain($url_parts['url']); } - $item['url'] = check_plain($url); + if (!empty($url_parts['url'])) { + $item['url'] = url($url_parts['url'], + array( + 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, + 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, + 'absolute' => TRUE, + 'html' => TRUE, + ) + ); + } // Create a shortened URL for display. - $display_url = $type == LINK_EMAIL ? - str_replace('mailto:', '', $url) : - url($url, array('query' => isset($item['query']) ? - $item['query'] : - NULL, - 'fragment' => isset($item['fragment']) ? - $item['fragment'] : - NULL, - 'absolute' => TRUE)); + if ($type == LINK_EMAIL) { + $display_url = str_replace('mailto:', '', $url); + } + else { + $display_url = url($url_parts['url'], + array( + 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, + 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, + 'absolute' => TRUE, + ) + ); + } if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) { - $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) ."..."; + $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "..."; } $item['display_url'] = $display_url; // Use the title defined at the instance level. if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) { $title = $instance['settings']['title_value']; + if (function_exists('i18n_string_translate')) { + $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value"; + $title = i18n_string_translate($i18n_string_name, $title); + } } // Use the title defined by the user at the widget level. - else if (isset($item['title'])) { + elseif (isset($item['title'])) { $title = $item['title']; } else { @@ -466,10 +491,16 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { // Replace tokens. if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) { - // Load the node if necessary for nodes in views. - $token_node = isset($node->nid) ? node_load($node->nid) : $node; - $title = filter_xss(token_replace($title, array('node' => $token_node)), - array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); + // Load the entity if necessary for entities in views. + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $title = token_replace($title, array($entity_token_type => $entity_loaded)); + $title = filter_xss($title, array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); $item['html'] = TRUE; } $item['title'] = empty($title) ? $item['display_url'] : $title; @@ -484,7 +515,7 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { } // Add default attributes. - if (!is_array($instance['settings']['attributes'])){ + if (!is_array($instance['settings']['attributes'])) { $instance['settings']['attributes'] = _link_default_attributes(); } else { @@ -499,23 +530,35 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) { $item['attributes']['target'] = $instance['settings']['attributes']['target']; } + elseif ($item['attributes']['target'] == LINK_TARGET_USER) { + $item['attributes']['target'] = LINK_TARGET_DEFAULT; + } // Remove the target attribute if the default (no target) is selected. - if (empty($item['attributes']) || $item['attributes']['target'] == LINK_TARGET_DEFAULT) { + if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) { unset($item['attributes']['target']); } - // Remove the rel=nofollow for internal links. - if ($type != LINK_EXTERNAL && strpos($item['attributes']['rel'], 'nofollow') !== FALSE) { - $item['attributes']['rel'] = str_replace('nofollow', '', $item['attributes']); + // Remove rel attribute for internal or external links if selected. + if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') { + if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) || + ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) { + unset($item['attributes']['rel']); + } } // Handle "title" link attribute. if (!empty($item['attributes']['title']) && module_exists('token')) { - // Load the node (necessary for nodes in views). - $token_node = isset($node->nid) ? node_load($node->nid) : $node; - $item['attributes']['title'] = filter_xss(token_replace($item['attributes']['title'], array('node' => $token_node)), - array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); + // Load the entity (necessary for entities in views). + if (isset($entity->{$property_id})) { + $entity_loaded = entity_load($entity_type, array($entity->{$property_id})); + $entity_loaded = array_pop($entity_loaded); + } + else { + $entity_loaded = $entity; + } + $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded)); + $item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); } // Remove title attribute if it's equal to link text. if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) { @@ -527,14 +570,46 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { $item['attributes'] = array_filter($item['attributes']); // Sets title to trimmed url if one exists - // @TODO: Do we need this? It seems not. + // @todo: Obsolete? /*if(!empty($item['display_url']) && empty($item['title'])) { $item['title'] = $item['display_url']; } elseif(!isset($item['title'])) { $item['title'] = $item['url']; }*/ +} +/** + * Because parse_url doesn't work with relative urls. + * + * @param string $url + * URL to parse. + * + * @return Array + * Array of url pieces - only 'url', 'query', and 'fragment'. + */ +function _link_parse_url($url) { + $url_parts = array(); + // Separate out the anchor, if any. + if (strpos($url, '#') !== FALSE) { + $url_parts['fragment'] = substr($url, strpos($url, '#') + 1); + $url = substr($url, 0, strpos($url, '#')); + } + // Separate out the query string, if any. + if (strpos($url, '?') !== FALSE) { + $query = substr($url, strpos($url, '?') + 1); + parse_str($query, $query_array); + // See http://drupal.org/node/1710578 + foreach ($query_array as $key=> &$value) { + if ($value === '' && FALSE === strpos($query, $key . '=')) { + $value = NULL; + } + } + $url_parts['query'] = $query_array; + $url = substr($url, 0, strpos($url, '?')); + } + $url_parts['url'] = $url; + return $url_parts; } /** @@ -542,15 +617,18 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$node) { */ function link_theme() { return array( - /*'link_field_settings' => array( - 'variables' => array('element' => NULL), - ),*/ 'link_formatter_link_default' => array( 'variables' => array('element' => NULL), ), 'link_formatter_link_plain' => array( 'variables' => array('element' => NULL), ), + 'link_formatter_link_absolute' => array( + 'variables' => array('element' => NULL), + ), + 'link_formatter_link_domain' => array( + 'variables' => array('element' => NULL, 'display' => NULL), + ), 'link_formatter_link_title_plain' => array( 'variables' => array('element' => NULL), ), @@ -561,7 +639,7 @@ function link_theme() { 'variables' => array('element' => NULL), ), 'link_formatter_link_label' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_separate' => array( 'variables' => array('element' => NULL), @@ -573,31 +651,30 @@ function link_theme() { } /** - * FAPI theme for an individual text elements. + * Formats a link field widget. */ function theme_link_field($vars) { - drupal_add_css(drupal_get_path('module', 'link') .'/link.css'); - + drupal_add_css(drupal_get_path('module', 'link') . '/link.css'); $element = $vars['element']; // Prefix single value link fields with the name of the field. if (empty($element['#field']['multiple'])) { if (isset($element['url']) && !isset($element['title'])) { - unset($element['url']['#title']); + $element['url']['#title_display'] = 'invisible'; } } $output = ''; $output .= '<div class="link-field-subrow clearfix">'; if (isset($element['title'])) { - $output .= '<div class="link-field-title link-field-column">'. drupal_render($element['title']) .'</div>'; + $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>'; } - $output .= '<div class="link-field-url'. (isset($element['title']) ? ' link-field-column' : '') .'">'. drupal_render($element['url']) .'</div>'; + $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">'. drupal_render($element['url']) . '</div>'; $output .= '</div>'; if (!empty($element['attributes']['target'])) { - $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['target']) .'</div>'; + $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>'; } if (!empty($element['attributes']['title'])) { - $output .= '<div class="link-attributes">'. drupal_render($element['attributes']['title']) .'</div>'; + $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>'; } return $output; } @@ -607,7 +684,7 @@ function theme_link_field($vars) { */ function link_element_info() { $elements = array(); - $elements['link_field'] = array( + $elements['link_field'] = array( '#input' => TRUE, '#process' => array('link_field_process'), '#theme' => 'link_field', @@ -616,6 +693,9 @@ function link_element_info() { return $elements; } +/** + * Returns the default attributes and their values. + */ function _link_default_attributes() { return array( 'target' => LINK_TARGET_DEFAULT, @@ -625,7 +705,7 @@ function _link_default_attributes() { } /** - * Process the link type element before displaying the field. + * Processes the link type element before displaying the field. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. @@ -645,10 +725,10 @@ function link_field_process($element, $form_state, $complete_form) { if ($settings['title'] !== 'none' && $settings['title'] !== 'value') { $element['title'] = array( '#type' => 'textfield', - '#maxlength' => $settings['title_maxlength'], // patch #1307788 from nmc + '#maxlength' => $settings['title_maxlength'], '#title' => t('Title'), - '#description' => t('The link title is limited to '.$settings['title_maxlength'].' characters maximum.'), // patch #1307788 from nmc - '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, // davereids patch from jan 2011 + '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])), + '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL, ); } @@ -678,15 +758,19 @@ function link_field_process($element, $form_state, $complete_form) { ); } - // To prevent an extra required indicator, disable the required flag on the - // base element since all the sub-fields are already required if desired. - $element['#required'] = FALSE; // davereids patch from jan 2011 + // If the title field is avaliable or there are field accepts multiple values + // then allow the individual field items display the required asterisk if needed. + if (isset($element['title']) || isset($element['_weight'])) { + // To prevent an extra required indicator, disable the required flag on the + // base element since all the sub-fields are already required if desired. + $element['#required'] = FALSE; + } return $element; } /** - * Implementation of hook_field_formatter_info(). + * Implements hook_field_formatter_info(). */ function link_field_formatter_info() { return array( @@ -710,6 +794,19 @@ function link_field_formatter_info() { 'field types' => array('link_field'), 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), + 'link_absolute' => array( + 'label' => t('URL, absolute'), + 'field types' => array('link_field'), + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + ), + 'link_domain' => array( + 'label' => t('Domain, as link'), + 'field types' => array('link_field'), + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'settings' => array( + 'strip_www' => FALSE, + ), + ), 'link_short' => array( 'label' => t('Short, as link with title "Link"'), 'field types' => array('link_field'), @@ -728,6 +825,40 @@ function link_field_formatter_info() { ); } +/** + * Implements hook_field_formatter_settings_form(). + */ +function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $element = array(); + if ($display['type'] == 'link_domain') { + $element['strip_www'] = array( + '#title' => t('Strip www. from domain'), + '#type' => 'checkbox', + '#default_value' => $settings['strip_www'], + ); + } + return $element; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function link_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + if ($display['type'] == 'link_domain') { + if ($display['settings']['strip_www']) { + return t('Strip www. from domain'); + } + else { + return t('Leave www. in domain'); + } + } + return ''; +} + /** * Implements hook_field_formatter_view(). */ @@ -735,25 +866,27 @@ function link_field_formatter_view($entity_type, $entity, $field, $instance, $la $elements = array(); foreach ($items as $delta => $item) { $elements[$delta] = array( - '#markup' => theme('link_formatter_'. $display['type'], array('element' => $item, 'field' => $instance)), + '#theme' => 'link_formatter_' . $display['type'], + '#element' => $item, + '#field' => $instance, + '#display' => $display, ); } return $elements; } /** - * Theme function for 'default' text field formatter. + * Formats a link. */ function theme_link_formatter_link_default($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); - - // Issue #1199806 by ss81: Fixes fatal error when the link URl is equal to page URL + unset($link_options['title']); + unset($link_options['url']); + if (isset($link_options['attributes']['class'])) { $link_options['attributes']['class'] = array($link_options['attributes']['class']); } - + // Display a normal link if both title and URL are available. if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) { return l($vars['element']['title'], $vars['element']['url'], $link_options); @@ -768,89 +901,114 @@ function theme_link_formatter_link_default($vars) { } /** - * Theme function for 'plain' text field formatter. + * Formats a link (or its title) as plain text. */ function theme_link_formatter_link_plain($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); - return empty($vars['element']['url']) ? - check_plain($vars['element']['title']) : - url($vars['element']['url'], $link_options); + if (isset($link_options['title'])) { + unset($link_options['title']); + } + else { + $vars['element']['title'] = ''; + } + unset($link_options['url']); + return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options); +} + +/** + * Formats a link as an absolute URL + */ +function theme_link_formatter_link_absolute($vars) { + $absolute = array('absolute' => TRUE); + return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']); } /** - * Theme function for 'title_plain' text field formatter. + * Formats a link using the URL's domain for it's link text. + */ +function theme_link_formatter_link_domain($vars) { + $link_options = $vars['element']; + unset($link_options['title']); + unset($link_options['url']); + $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST); + if (!empty($vars['display']['settings']['strip_www'])) { + $domain = str_replace('www.', '', $domain); + } + return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : ''; +} + +/** + * Formats a link's title as plain text. */ function theme_link_formatter_link_title_plain($vars) { return empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']); } /** - * Theme function for 'url' text field formatter. + * Formats a link using an alternate display URL for its link text. */ function theme_link_formatter_link_url($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : ''; } /** - * Theme function for 'short' text field formatter. + * Formats a link using "Link" as the link text. */ function theme_link_formatter_link_short($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : ''; } /** - * Theme function for 'label' text field formatter. + * Formats a link using the field's label as link text. */ function theme_link_formatter_link_label($vars) { $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : ''; } /** - * Theme function for 'separate' text field formatter. + * Formats a link as separate title and URL elements. */ - function theme_link_formatter_link_separate($vars) { - $class = empty($vars['element']['attributes']['class']) ? '' : ' '. $vars['element']['attributes']['class']; + $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class']; unset($vars['element']['attributes']['class']); $link_options = $vars['element']; - unset($link_options['element']['title']); - unset($link_options['element']['url']); + unset($link_options['title']); + unset($link_options['url']); $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']); - - /** - * @TODO static html markup looks not very elegant to me (who takes it off?) - * needs smarter output solution and an optional title/url seperator (digidog) + + /** + * @TODO static html markup looks not very elegant + * needs smarter output solution and an optional title/url seperator */ + $url_parts = _link_parse_url($vars['element']['url']); $output = ''; - $output .= '<div class="link-item '. $class .'">'; + $output .= '<div class="link-item ' . $class . '">'; if (!empty($title)) { - $output .= '<div class="link-title">'. $title .'</div>'; + $output .= '<div class="link-title">' . $title . '</div>'; } - $output .= '<div class="link-url">'. l($vars['element']['url'], $vars['element']['url'], $link_options) .'</div>'; + $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>'; $output .= '</div>'; return $output; } - +/** + * Implements hook_token_list(). + */ function link_token_list($type = 'all') { if ($type === 'field' || $type === 'all') { $tokens = array(); - $tokens['link']['url'] = t("Link URL"); $tokens['link']['title'] = t("Link title"); $tokens['link']['view'] = t("Formatted html link"); - return $tokens; } } @@ -873,18 +1031,21 @@ function link_token_values($type, $object = NULL) { function link_views_api() { return array( 'api' => 2, - 'path' => drupal_get_path('module', 'link') .'/views', + 'path' => drupal_get_path('module', 'link') . '/views', ); } /** * Forms a valid URL if possible from an entered address. - * Trims whitespace and automatically adds an http:// to addresses without a protocol specified + * + * Trims whitespace and automatically adds an http:// to addresses without a + * protocol specified * * @param string $url - * @param string $protocol The protocol to be prepended to the url if one is not specified + * @param string $protocol + * The protocol to be prepended to the url if one is not specified */ -function link_cleanup_url($url, $protocol = "http") { +function link_cleanup_url($url, $protocol = 'http') { $url = trim($url); $type = link_validate_url($url); @@ -893,9 +1054,10 @@ function link_cleanup_url($url, $protocol = "http") { $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url); if (empty($protocol_match)) { // But should there be? Add an automatic http:// if it starts with a domain name. - $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)('. LINK_DOMAINS .'|[a-z]{2}))/i', $url); + $LINK_DOMAINS = _link_domains(); + $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z]{2}))/i', $url); if (!empty($domain_match)) { - $url = $protocol ."://". $url; + $url = $protocol . "://" . $url; } } } @@ -904,16 +1066,20 @@ function link_cleanup_url($url, $protocol = "http") { } /** - * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard - * for URL formation and all email addresses following the RFC 2368 standard for - * mailto address formation. + * Validates a URL. + * + * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail + * addresses following the RFC 2368 standard for mailto address formation. * * @param string $text - * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with - * the following attributes: protocol, hostname, ip, and port. + * + * @return mixed + * Returns boolean FALSE if the URL is not valid. On success, returns one of + * the LINK_(linktype) constants. */ function link_validate_url($text) { - $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( // @TODO completing letters ... + // @TODO Complete letters. + $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( "æ", // æ "Æ", // Æ "À", // À @@ -948,8 +1114,8 @@ function link_validate_url($text) { "Ö", // Ö "Ô", // Ô "ô", // ô - "Õ", // Õ - "õ", // õ + "Õ", // Õ + "õ", // õ "Œ", // Œ "œ", // œ "ü", // ü @@ -959,7 +1125,7 @@ function link_validate_url($text) { "Û", // Û "û", // û "Ÿ", // Ÿ - "ÿ", // ÿ + "ÿ", // ÿ "Ñ", // Ñ "ñ", // ñ "þ", // þ @@ -973,36 +1139,37 @@ function link_validate_url($text) { "ß", // ß )), ENT_QUOTES, 'UTF-8'); $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal')); + $LINK_DOMAINS = _link_domains(); // Starting a parenthesis group with (?: means that it is grouped, but is not captured - $protocol = '((?:'. implode("|", $allowed_protocols) .'):\/\/)'; - $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w". $LINK_ICHARS ."\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)"; - $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9'. $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*('. LINK_DOMAINS .'|[a-z]{2}))?)'; + $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)'; + $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w" . $LINK_ICHARS . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)"; + $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*(' . $LINK_DOMAINS . '|[a-z]{2}))?)'; $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})'; $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})'; $port = '(?::([0-9]{1,5}))'; // Pattern specific to external links. - $external_pattern = '/^'. $protocol .'?'. $authentication .'?('. $domain .'|'. $ipv4 .'|'. $ipv6 .' |localhost)'. $port .'?'; + $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?'; // Pattern specific to internal links. - $internal_pattern = "/^(?:[a-z0-9". $LINK_ICHARS ."_\-+\[\]]+)"; - $internal_pattern_file = "/^(?:[a-z0-9". $LINK_ICHARS ."_\-+\[\]\.]+)$/i"; + $internal_pattern = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\] ]+)"; + $internal_pattern_file = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)]+)$/i"; - $directories = "(?:\/[a-z0-9". $LINK_ICHARS ."_\-\.~+%=&,$'#!():;*@\[\]]*)*"; + $directories = "(?:\/[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'#!():;*@\[\]]*)*"; // Yes, four backslashes == a single backslash. - $query = "(?:\/?\?([?a-z0-9". $LINK_ICHARS ."+_|\-\.~\/\\\\%=&,$'():;*@\[\]{} ]*))"; - $anchor = "(?:#[a-z0-9". $LINK_ICHARS ."_\-\.~+%=&,$'():;*@\[\]\/\?]*)"; + $query = "(?:\/?\?([?a-z0-9" . $LINK_ICHARS . "+_|\-\.~\/\\\\%=&,$'():;*@\[\]{} ]*))"; + $anchor = "(?:#[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'():;*@\[\]\/\?]*)"; // The rest of the path for a standard URL. - $end = $directories .'?'. $query .'?'. $anchor .'?'.'$/i'; + $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i'; - $message_id = '[^@].*@'. $domain; + $message_id = '[^@].*@' . $domain; $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*'; - $news_pattern = '/^news:('. $newsgroup_name .'|'. $message_id .')$/i'; + $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i'; - $user = '[a-zA-Z0-9'. $LINK_ICHARS .'_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+'; - $email_pattern = '/^mailto:'. $user .'@'.'(?:'. $domain .'|'. $ipv4 .'|'. $ipv6 .'|localhost)'. $query .'?$/'; + $user = '[a-zA-Z0-9' . $LINK_ICHARS . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+'; + $email_pattern = '/^mailto:' . $user . '@'.'(?:' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/'; if (strpos($text, '<front>') === 0) { return LINK_FRONT; @@ -1026,13 +1193,25 @@ function link_validate_url($text) { return FALSE; } +/** + * Returns the list of allowed domains, including domains added by admins via variable_set/$config. + */ +function _link_domains() { + $link_extra_domains = variable_get('link_extra_domains', array()); + return empty($link_extra_domains) ? LINK_DOMAINS : LINK_DOMAINS . '|' . implode('|', $link_extra_domains); +} + /** * Implements hook_migrate_field_alter(). */ function link_content_migrate_field_alter(&$field_value, $instance_value) { if ($field_value['type'] == 'link') { - // need to change the type: + // Adjust the field type. $field_value['type'] = 'link_field'; + // Remove settings that are now on the instance. + foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) { + unset($field_value['settings'][$setting]); + } } } @@ -1042,9 +1221,23 @@ function link_content_migrate_field_alter(&$field_value, $instance_value) { * Widget type also changed to link_field. */ function link_content_migrate_instance_alter(&$instance_value, $field_value) { - if ($instance_value['widget']['module'] == 'link' - && $instance_value['widget']['type'] == 'link') { - $instance_value['widget']['type'] = 'link_field'; + if ($field_value['type'] == 'link') { + // Grab settings that were previously on the field. + foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) { + if (isset($field_value['settings'][$setting])) { + $instance_value['settings'][$setting] = $field_value['settings'][$setting]; + } + } + // Adjust widget type. + if ($instance_value['widget']['type'] == 'link') { + $instance_value['widget']['type'] = 'link_field'; + } + // Adjust formatter types. + foreach ($instance_value['display'] as $context => $settings) { + if (in_array($settings['type'], array('default', 'title_plain', 'url', 'plain', 'short', 'label', 'separate'))) { + $instance_value['display'][$context]['type'] = 'link_' . $settings['type']; + } + } } } @@ -1057,6 +1250,7 @@ function link_field_settings_form() { /** * Additional callback to adapt the property info of link fields. + * * @see entity_metadata_field_entity_property_info(). */ function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) { @@ -1067,7 +1261,7 @@ function link_field_property_info_callback(&$info, $entity_type, $field, $instan $property['setter callback'] = 'entity_metadata_field_verbatim_set'; // Auto-create the field item as soon as a property is set. - $property['auto creation'] = 'link_field_item_create'; + $property['auto creation'] = 'link_field_item_create'; $property['property info'] = link_field_item_property_info(); $property['property info']['url']['required'] = !$instance['settings']['url']; @@ -1075,7 +1269,6 @@ function link_field_property_info_callback(&$info, $entity_type, $field, $instan if ($instance['settings']['title'] == 'none') { unset($property['property info']['title']); } - unset($property['query callback']); } @@ -1104,3 +1297,24 @@ function link_field_item_property_info() { ); return $properties; } + +/** + * Implements hook_field_update_instance(). + */ +function link_field_update_instance($instance, $prior_instance) { + if (function_exists('i18n_string_update') && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) { + $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value"; + i18n_string_update($i18n_string_name, $instance['settings']['title_value']); + } +} + +/** + * Implements hook_i18n_string_list_TEXTGROUP_alter(). + */ +function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) { + if ($type == 'field_instance' && $object && $object['widget']['type'] == 'link_field') { + if (isset($object['settings']['title_value'])) { + $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value']; + } + } +} diff --git a/sites/all/modules/link/tests/link.attribute.test b/sites/all/modules/link/tests/link.attribute.test index 398713a04e640c42fe167c5881ab26ccfb31c230..97ac2a478fc77a6a9aecc08a4606f65c1c925884 100644 --- a/sites/all/modules/link/tests/link.attribute.test +++ b/sites/all/modules/link/tests/link.attribute.test @@ -6,10 +6,9 @@ */ class LinkAttributeCrudTest extends DrupalWebTestCase { - private $zebra; - public $permissions = array( + protected $permissions = array( 'access content', 'administer content types', 'administer nodes', @@ -29,15 +28,14 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { } function setup() { + parent::setup('field_ui', 'link'); $this->zebra = 0; - parent::setup('field_ui', 'link'); // was 'views' - //$this->loginWithPermissions($this->permissions); // Create and login user. - $account = $this->drupalCreateUser(array('administer content types')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types')); + $this->drupalLogin($this->web_user); } - function createLink($url, $title, $attributes = array()) { + protected function createLink($url, $title, $attributes = array()) { return array( 'url' => $url, 'title' => $title, @@ -45,7 +43,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { ); } - private function assertLinkOnNode($field_name, $link_value, $message = '', $group = 'Other') { + protected function assertLinkOnNode($field_name, $link_value, $message = '', $group = 'Other') { $this->zebra++; $zebra_string = ($this->zebra % 2 == 0) ? 'even' : 'odd'; $cssFieldLocator = 'field-'. str_replace('_', '-', $field_name); @@ -59,9 +57,6 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { * that the node is being displayed. */ function testBasic() { - /*$this->acquireContentTypes(1); - variable_set('node_options_'. $this->content_types[0]->name, array('status', 'promote'));*/ - $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); $title = $this->randomName(20); @@ -113,8 +108,8 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { // Now that we have a new content type, create a user that has privileges // on the content type. $permissions = array_merge($this->permissions, array($permission)); - $account = $this->drupalCreateUser($permissions); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->web_user); // Go to page. $this->drupalGet('node/add/'. $content_type_machine); @@ -129,33 +124,9 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save')); $this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $title))); - /*$field_settings = array( - 'type' => 'link', - 'widget_type' => 'link', - 'type_name' => $this->content_types[0]->name, - 'attributes' => array(), // <-- This is needed or we have an error. - ); - - $field = $this->createField($field_settings, 0); - //$this->pass('<pre>'. print_r($field, TRUE) .'</pre>'); - $field_db_info = content_database_info($field);*/ - - //$this->acquireNodes(2); - /*$node = $this->drupalCreateNode(array('type' => $content_type_machine, - 'promote' => 1)); - $test_nid = $node->nid;*/ - - //$node = node_load($this->nodes[0]->nid); - //$node->promote = 1; // We want this to show on front page for the teaser test. - /*$this->assert('debug', print_r($node, TRUE), 'Debug'); - $node->{$single_field_name}['und'][0] = $this->createLink('http://www.example.com', 'Test Link'); - node_save($node); - $this->assert('debug', print_r($node, TRUE), 'Debug');*/ - - //$this->drupalGet('node/'. $test_nid .'/edit'); $this->drupalGet('node/add/'. $content_type_machine); - // lets add a node: + // Create a node: $edit = array( 'title' => $title, 'field_' . $single_field_name_machine . '[und][0][url]' => 'http://www.example.com/', @@ -167,25 +138,11 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $title))); $this->assertText('Display'); - //$this->assertText('http://www.example.com/'); $this->assertLinkByHref('http://www.example.com'); } - private function createNodeType($content_type_machine, $content_type_friendly) { - $this->drupalGet('admin/structure/types'); - - // Create the content type. - $this->clickLink(t('Add content type')); - - $edit = array ( - 'name' => $content_type_friendly, - 'type' => $content_type_machine, - ); - $this->drupalPost(NULL, $edit, t('Save and add fields')); - $this->assertText(t('The content type @name has been added.', array('@name' => $content_type_friendly))); - } - - private function createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine) { + protected function createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine) { + $this->drupalGet('admin/structure/types/manage/' . $content_type_machine . '/fields'); $edit = array ( 'fields[_add_new_field][label]' => $single_field_name_friendly, 'fields[_add_new_field][field_name]' => $single_field_name_machine, @@ -209,7 +166,7 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->assertTrue($type_exists, 'The new content type has been created in the database.'); } - function createNodeTypeUser($content_type_machine) { + protected function createNodeTypeUser($content_type_machine) { $permission = 'create ' . $content_type_machine . ' content'; $permission_edit = 'edit ' . $content_type_machine . ' content'; // Reset the permissions cache. @@ -218,12 +175,11 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { // Now that we have a new content type, create a user that has privileges // on the content type. $permissions = array_merge($this->permissions, array($permission)); - $account = $this->drupalCreateUser($permissions); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->web_user); } - function createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $title, $url, $node_title = '') { - // Go to page. + protected function createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $title, $url, $node_title = '') { $this->drupalGet('node/add/'. $content_type_machine); if (!$node_title) { @@ -239,48 +195,22 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $edit['field_' . $single_field_name_machine . '[und][0][title]'] = $title; } - // Now we can fill in the second item in the multivalue field and save. $this->drupalPost(NULL, $edit, t('Save')); $this->assertText(t('@content_type_friendly @title has been created', array('@content_type_friendly' => $content_type_friendly, '@title' => $node_title))); } + /** + * Test the link_plain formatter and it's output. + */ function testFormatterPlain() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_plain', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_url); - $this->assertNoText($link_text); - $this->assertNoLinkByHref($link_url); - } - - function testFormatterPlainWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -297,82 +227,41 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save')); $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_url); - $this->assertNoText($link_text); - $this->assertNoLinkByHref($link_url); - } - - function testFormatterPlainWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_plain', + + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - $this->assertText($link_url); - $this->assertNoText($link_text); - $this->assertNoLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText($link_url); + $this->assertNoText($link_text); + $this->assertNoLinkByHref($link_url); + } } function testFormatterURL() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_url', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterURLWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -389,81 +278,40 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save')); $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterURLWithAnchor() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_url', + + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertNoText($link_text); + $this->assertLinkByHref($link_url); + } } function testFormatterShort() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_short', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText('Link'); - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterShortWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -481,82 +329,40 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText('Link'); - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); - } - - function testFormatterShortWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_short', + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText('Link'); - $this->assertNoText($link_text); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText('Link'); + $this->assertNoText($link_text); + $this->assertLinkByHref($link_url); + } } function testFormatterLabel() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_label', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertText($single_field_name_friendly); - $this->assertLinkByHref($link_url); - } - - function testFormatterLabelWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -574,82 +380,40 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->createNodeTypeUser($content_type_machine); - $link_text = 'Display'; - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertText($single_field_name_friendly); - $this->assertLinkByHref($link_url); - } - - function testFormatterLabelWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_label', + $link_tests = array( + 'plain' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/', + ), + 'query' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/?q=test', + ), + 'fragment' => array( + 'text' => 'Display', + 'url' => 'http://www.example.com/#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->createNodeTypeUser($content_type_machine); - - $link_text = 'Display'; - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertNoText($link_text); - $this->assertText($single_field_name_friendly); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertNoText($link_text); + $this->assertText($single_field_name_friendly); + $this->assertLinkByHref($link_url); + } } function testFormatterSeparate() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_separate', - ); - $this->drupalPost(NULL, $edit, t('Save')); - - $this->createNodeTypeUser($content_type_machine); - - $link_text = $this->randomName(20); - $link_url = 'http://www.example.com/'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_text); - $this->assertLink($link_url); - $this->assertLinkByHref($link_url); - } - - function testFormatterSeparateWithQuerystring() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -667,51 +431,41 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->createNodeTypeUser($content_type_machine); - $link_text = $this->randomName(20); - $link_url = 'http://www.example.com/?q=test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_text); - $this->assertLink('http://www.example.com/'); - $this->assertLinkByHref($link_url); - } - - function testFormatterSeparateWithFragment() { - $content_type_friendly = $this->randomName(20); - $content_type_machine = strtolower($this->randomName(10)); - - $this->createNodeType($content_type_machine, $content_type_friendly); - - // Now add a singleton field. - $single_field_name_friendly = $this->randomName(20); - $single_field_name_machine = strtolower($this->randomName(10)); - //$single_field_name = 'field_'. $single_field_name_machine; - $this->createSimpleLinkField($single_field_name_machine, $single_field_name_friendly, $content_type_machine); - - // Okay, now we want to make sure this display is changed: - $this->drupalGet('admin/structure/types/manage/'. $content_type_machine .'/display'); - $edit = array( - 'fields[field_'. $single_field_name_machine .'][label]' => 'above', - 'fields[field_'. $single_field_name_machine .'][type]' => 'link_separate', + $plain_url = 'http://www.example.com/'; + $link_tests = array( + 'plain' => array( + 'text' => $this->randomName(20), + 'url' => $plain_url, + ), + 'query' => array( + 'text' => $this->randomName(20), + 'url' => $plain_url . '?q=test', + ), + 'fragment' => array( + 'text' => $this->randomName(20), + 'url' => $plain_url . '#test', + ), ); - $this->drupalPost(NULL, $edit, t('Save')); - $this->createNodeTypeUser($content_type_machine); - - $link_text = $this->randomName(20); - $link_url = 'http://www.example.com/#test'; - $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); - - $this->assertText($link_text); - $this->assertLink('http://www.example.com/'); - $this->assertLinkByHref($link_url); + foreach ($link_tests as $key => $link_test) { + $link_text = $link_test['text']; + $link_url = $link_test['url']; + $this->createNodeForTesting($content_type_machine, $content_type_friendly, $single_field_name_machine, $link_text, $link_url); + + $this->assertText($link_text); + $this->assertLink($plain_url); + $this->assertLinkByHref($link_url); + } } function testFormatterPlainTitle() { $content_type_friendly = $this->randomName(20); $content_type_machine = strtolower($this->randomName(10)); - $this->createNodeType($content_type_machine, $content_type_friendly); + $this->drupalCreateContentType(array( + 'type' => $content_type_machine, + 'name' => $content_type_friendly, + )); // Now add a singleton field. $single_field_name_friendly = $this->randomName(20); @@ -737,45 +491,4 @@ class LinkAttributeCrudTest extends DrupalWebTestCase { $this->assertNoText($link_url); $this->assertNoLinkByHref($link_url); } - - /** - * This test sees that we can create a link field with a defined class, and make sure - * that class displays properly when the link is displayed. - */ - /*function testLinkWithClassOnField() { - $this->acquireContentTypes(1); - $field_settings = array( - 'type' => 'link', - 'widget_type' => 'link', - 'type_name' => $this->content_types[0]->name, - 'attributes' => array( - 'class' => 'test-class', - 'target' => 'default', - 'rel' => FALSE, - ), - ); - - $field = $this->createField($field_settings, 0); - //$this->pass('<pre>'. print_r($field, TRUE) .'</pre>'); - $field_db_info = content_database_info($field); - - $this->acquireNodes(2); - - $node = node_load($this->nodes[0]->nid); - $node->promote = 1; // We want this to show on front page for the teaser test. - $node->{$field['field_name']}[0] = $this->createLink('http://www.example.com', 'Test Link'); - node_save($node); - - // Does this display on the node page? - $this->drupalGet('node/'. $this->nodes[0]->nid); - //$this->outputScreenContents('Link field with class', 'link_'); - $this->assertLinkOnNode($field['field_name'], l('Test Link', 'http://www.example.com', array('attributes' => array('class' => 'test-class')))); - - // Does this display on the front page? - $this->drupalGet('<front>'); - // reset the zebra! - $this->zebra = 0; - $this->assertLinkOnNode($field['field_name'], l('Test Link', 'http://www.example.com', array('attributes' => array('class' => 'test-class')))); - }*/ - } diff --git a/sites/all/modules/link/tests/link.crud.test b/sites/all/modules/link/tests/link.crud.test index cdd7fc5aec4fb7ab27f96a8de19d5ec104cd97cf..54889fd298b504c8203e9de346a8196262df3f7e 100644 --- a/sites/all/modules/link/tests/link.crud.test +++ b/sites/all/modules/link/tests/link.crud.test @@ -16,8 +16,7 @@ class LinkContentCrudTest extends DrupalWebTestCase { } function setUp() { - parent::setUp('field_ui', 'link'); // was views? - //$this->loginWithPermissions(); + parent::setUp('field_ui', 'link'); } /** @@ -30,15 +29,14 @@ class LinkContentCrudTest extends DrupalWebTestCase { $title = $this->randomName(20); // Create and login user. - $account = $this->drupalCreateUser(array('administer content types')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types')); + $this->drupalLogin($this->web_user); $this->drupalGet('admin/structure/types'); // Create the content type. $this->clickLink(t('Add content type')); - $edit = array ( 'name' => $content_type_friendly, 'type' => $content_type_machine, @@ -55,7 +53,6 @@ class LinkContentCrudTest extends DrupalWebTestCase { 'fields[_add_new_field][field_name]' => $single_field_name_machine, 'fields[_add_new_field][type]' => 'link_field', 'fields[_add_new_field][widget_type]' => 'link_field', - ); $this->drupalPost(NULL, $edit, t('Save')); diff --git a/sites/all/modules/link/tests/link.crud_browser.test b/sites/all/modules/link/tests/link.crud_browser.test index 7b01da0ff75e2e2c1d8ba06a0fc5f416dbcf6491..95406018089a663e5584f8a93e6358e7caf7d0c9 100644 --- a/sites/all/modules/link/tests/link.crud_browser.test +++ b/sites/all/modules/link/tests/link.crud_browser.test @@ -42,13 +42,15 @@ class LinkUITest extends DrupalWebTestcase { */ function testLinkCreate() { //libxml_use_internal_errors(true); - $account = $this->drupalCreateUser(array('administer content types', - 'administer nodes', - 'administer filters', - 'access content', - 'create page content', - 'access administration pages')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array( + 'administer content types', + 'administer nodes', + 'administer filters', + 'access content', + 'create page content', + 'access administration pages' + )); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -125,7 +127,7 @@ class LinkUITest extends DrupalWebTestcase { $input_test_cases[] = $test_case; foreach ($input_test_cases as $input) { - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -135,7 +137,7 @@ class LinkUITest extends DrupalWebTestcase { ); $this->drupalPost(NULL, $edit, t('Save')); if ($input['type'] == self::LINK_INPUT_TYPE_BAD_URL) { - $this->assertRaw(t('Not a valid URL.'), 'Not a valid URL: ' . $input['href']); + $this->assertRaw(t('The value provided for %field is not a valid URL.', array('%field' => $name)), 'Not a valid URL: ' . $input['href']); continue; } else { @@ -167,122 +169,13 @@ class LinkUITest extends DrupalWebTestcase { //libxml_use_internal_errors(FALSE); } - /** - * Creates a link field for the "page" type and creates a page with a link. - * Just like the above test, only here we're turning off the validation on the field. - */ - /*function testLinkCreate_NoValidation() { - //libxml_use_internal_errors(true); - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); - - // create field - $name = strtolower($this->randomName()); - $edit = array( - 'fields[_add_new_field][label]' => $name, - 'fields[_add_new_field][field_name]' => $name, - 'fields[_add_new_field][type]' => 'link_field', - 'fields[_add_new_field][widget_type]' => 'link_field', - ); - $this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save')); - $this->drupalPost(NULL, array(), t('Save field settings')); - $this->drupalPost(NULL, array('validate_url' => FALSE), t('Save settings')); - - // Is field created? - $this->assertRaw(t('Saved %label configuration', array('%label' => $name)), 'Field added'); - _content_type_info(TRUE); - $fields = content_fields(); - $this->assertTRUE(0 === $fields['field_'. $name]['validate_url'], 'Make sure validation is off.'); - - // create page form - $this->drupalGet('node/add/page'); - $field_name = 'field_' . $name; - $this->assertField($field_name . '[0][title]', 'Title found'); - $this->assertField($field_name . '[0][url]', 'URL found'); - - $input_test_cases = array( - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName(), - 'msg' => 'Link found', - 'type' => self::LINK_INPUT_TYPE_GOOD - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '<script>alert("hi");</script>', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '<script src="http://devil.site.com"></script>', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '" onmouseover="alert(\'hi\')', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'http://example.com/' . $this->randomName(), - 'label' => $this->randomName() . '\' onmouseover="alert(\'hi\')', - 'msg' => 'js label', - 'type' => self::LINK_INPUT_TYPE_BAD_TITLE - ), - array( - 'href' => 'javascript:alert("http://example.com/' . $this->randomName() . '")', - 'label' => $this->randomName(), - 'msg' => 'js url', - 'type' => self::LINK_INPUT_TYPE_BAD_URL - ), - ); - foreach ($input_test_cases as $input) { - $this->drupalLogin($account); - $this->drupalGet('node/add/page'); - - $edit = array( - 'title' => $input['label'], - $field_name . '[0][title]' => $input['label'], - $field_name . '[0][url]' => $input['href'], - ); - $this->drupalPost(NULL, $edit, t('Save')); - if ($input['type'] == self::LINK_INPUT_TYPE_BAD_URL) { - //$this->assertRaw(t('Not a valid URL.'), 'Not a valid URL: ' . $input['href']); - $this->assertNoRaw($input['href']); - $this->assertRaw(t('@type %title has been created.', array('@type' => 'Basic Page', '%title' => $edit['title'])), 'Page created: ' . $input['href']); - continue; - } - else { - $this->assertRaw(t('@type %title has been created.', array('@type' => 'Basic Page', '%title' => $edit['title'])), 'Page created: ' . $input['href']); - } - $url = $this->getUrl(); - - // change to anonym user - $this->drupalLogout(); - - $this->drupalGet($url); - //debug($this); - // If simpletest starts using something to override the error system, this will flag - // us and let us know it's broken. - $this->assertFalse(libxml_use_internal_errors(TRUE)); - $path = '//a[@href="'. $input['href'] .'" and text()="'. $input['label'] .'"]'; - //$this->pass(htmlentities($path)); - $elements = $this->xpath($path); - libxml_use_internal_errors(FALSE); - $this->assertIdentical(isset($elements[0]), $input['type'] == self::LINK_INPUT_TYPE_GOOD, $input['msg']); - } - //libxml_use_internal_errors(FALSE); - }*/ - /** * Testing that if you use <strong> in a static title for your link, that the * title actually displays <strong>. */ function testStaticLinkCreate() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -330,8 +223,8 @@ class LinkUITest extends DrupalWebTestcase { * sure they are set to the expected results. */ function testCRUDCreateFieldDefaults() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); diff --git a/sites/all/modules/link/tests/link.test b/sites/all/modules/link/tests/link.test index dfb4dda337f0162710949d454b3b0bd8db00bf67..538e3a6769859e7c7007421b5839a1225e338b0d 100644 --- a/sites/all/modules/link/tests/link.test +++ b/sites/all/modules/link/tests/link.test @@ -6,7 +6,7 @@ */ class LinkBaseTestClass extends DrupalWebTestCase { - public $permissions = array( + protected $permissions = array( 'access content', 'administer content types', 'administer nodes', @@ -17,21 +17,18 @@ class LinkBaseTestClass extends DrupalWebTestCase { 'create page content', ); - public $account; - - function setUp($modules = array()) { - if ($modules) { - parent::setUp($modules); - } - else { - parent::setUp('field_ui', 'link'); - } - $this->account = $this->drupalCreateUser($this->permissions); - $this->drupalLogin($this->account); + function setUp() { + $modules = func_get_args(); + $modules = (isset($modules[0]) && is_array($modules[0]) ? $modules[0] : $modules); + $modules[] = 'field_ui'; + $modules[] = 'link'; + parent::setUp($modules); + + $this->web_user = $this->drupalCreateUser($this->permissions); + $this->drupalLogin($this->web_user); } - function createLinkField($node_type = 'page', - $settings = array()) { + protected function createLinkField($node_type = 'page', $settings = array()) { $name = strtolower($this->randomName()); $edit = array( 'fields[_add_new_field][label]' => $name, diff --git a/sites/all/modules/link/tests/link.token.test b/sites/all/modules/link/tests/link.token.test index d23f46a78e54a675b4c5943e5831a12a97912551..de3ed3fef587b4fd77f7877e03032c5a593ca5e7 100644 --- a/sites/all/modules/link/tests/link.token.test +++ b/sites/all/modules/link/tests/link.token.test @@ -20,10 +20,7 @@ class LinkTokenTest extends LinkBaseTestClass { } function setUp($modules = array()) { - $modules[] = 'field_ui'; - $modules[] = 'link'; - $modules[] = 'token'; - parent::setUp($modules); + parent::setUp(array('token')); } /** @@ -31,9 +28,6 @@ class LinkTokenTest extends LinkBaseTestClass { * Creates a node with a token in the link title and checks the value. */ function testUserTokenLinkCreate() { - /*$account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account);*/ - // create field $settings = array( 'instance[settings][enable_tokens]' => 1, @@ -52,7 +46,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'label' => $this->randomName(), ); - //$this->drupalLogin($account); + //$this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -92,7 +86,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'href' => 'http://example.com/' . $this->randomName() ); - //$this->drupalLogin($account); + //$this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -135,7 +129,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'href' => 'http://example.com/' . $this->randomName() ); - //$this->drupalLogin($account); + //$this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -280,8 +274,8 @@ class LinkTokenTest extends LinkBaseTestClass { * Trying to set the url to contain a token. */ function _testUserTokenLinkCreateInURL() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -310,7 +304,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'label' => $this->randomName(), ); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -333,8 +327,8 @@ class LinkTokenTest extends LinkBaseTestClass { * Trying to set the url to contain a token. */ function _testUserTokenLinkCreateInURL2() { - $account = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); - $this->drupalLogin($account); + $this->web_user = $this->drupalCreateUser(array('administer content types', 'access content', 'create page content')); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -363,7 +357,7 @@ class LinkTokenTest extends LinkBaseTestClass { 'label' => $this->randomName(), ); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $edit = array( @@ -378,6 +372,6 @@ class LinkTokenTest extends LinkBaseTestClass { $this->drupalLogout(); $this->drupalGet($url); - $this->assertRaw(l($input['label'], $input['href'] .'/'. $account->uid)); + $this->assertRaw(l($input['label'], $input['href'] .'/'. $this->web_user->uid)); } } diff --git a/sites/all/modules/link/tests/link.validate.test b/sites/all/modules/link/tests/link.validate.test index affa7e94517f2d584b893d6d702083f8865f561f..0f721fbe181ed9c40118878e0d698d2b67ee2074 100644 --- a/sites/all/modules/link/tests/link.validate.test +++ b/sites/all/modules/link/tests/link.validate.test @@ -7,11 +7,7 @@ class LinkValidateTestCase extends LinkBaseTestClass { - function setUp($modules = array()) { - parent::setUp($modules); - } - - function createLink($url, $title, $attributes = array()) { + protected function createLink($url, $title, $attributes = array()) { return array( 'url' => $url, 'title' => $title, @@ -22,7 +18,7 @@ class LinkValidateTestCase extends LinkBaseTestClass { /** * Takes a url, and sees if it can validate that the url is valid. */ - public function link_test_validate_url($url) { + protected function link_test_validate_url($url) { $field_name = $this->createLinkField(); @@ -66,13 +62,13 @@ class LinkValidateTest extends LinkValidateTestCase { * Test if we're stopped from posting a bad url on default validation. */ function test_link_validate_bad_url_validate_default() { - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer filters', 'access content', 'create page content', 'access administration pages')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -104,20 +100,20 @@ class LinkValidateTest extends LinkValidateTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText(t('Not a valid URL.')); + $this->assertText(t('The value provided for @field is not a valid URL.', array('@field' => $name))); } /** * Test if we're stopped from posting a bad url with validation on. */ function test_link_validate_bad_url_validate_on() { - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer filters', 'access content', 'create page content', 'access administration pages')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -149,7 +145,7 @@ class LinkValidateTest extends LinkValidateTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText(t('Not a valid URL.')); + $this->assertText(t('The value provided for @field is not a valid URL.', array('@field' => $name))); } @@ -157,13 +153,13 @@ class LinkValidateTest extends LinkValidateTestCase { * Test if we can post a bad url if the validation is expressly turned off. */ function test_link_validate_bad_url_validate_off() { - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer filters', 'access content', 'create page content', 'access administration pages')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); // create field $name = strtolower($this->randomName()); @@ -199,8 +195,7 @@ class LinkValidateTest extends LinkValidateTestCase { ); $this->drupalPost(NULL, $edit, t('Save')); - $this->assertNoText(t('Not a valid URL.')); - + $this->assertNoText(t('The value provided for @field is not a valid URL.', array('@field' => $name))); } /** @@ -208,13 +203,13 @@ class LinkValidateTest extends LinkValidateTestCase { */ function x_test_link_validate_switching_between_validation_status() { $this->acquireContentTypes(1); - $account = $this->drupalCreateUser(array('administer content types', + $this->web_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'access administration pages', 'access content', 'create '. $this->content_types[0]->type .' content', 'edit any '. $this->content_types[0]->type .' content')); - $this->drupalLogin($account); + $this->drupalLogin($this->web_user); variable_set('node_options_'. $this->content_types[0]->name, array('status', 'promote')); $field_settings = array( 'type' => 'link', @@ -242,7 +237,7 @@ class LinkValidateTest extends LinkValidateTestCase { $this->drupalPost('node/'. $this->nodes[0]->nid .'/edit', $edit, t('Save')); //$this->pass($this->content); - $this->assertNoText(t('Not a valid URL.')); + $this->assertNoText(t('The value provided for %field is not a valid URL.', array('%field' => $name))); // Make sure we get a new version! $node = node_load($this->nodes[0]->nid, NULL, TRUE); @@ -361,11 +356,6 @@ class LinkValidateSpecificURL extends LinkValidateTestCase { */ class LinkValidateUrlLight extends DrupalWebTestCase { - //function setUp() { - // do we need to include something here? - //module_load_include('inc', 'link'); - //} - public static function getInfo() { return array( 'name' => 'Link Light Validation Tests', @@ -427,19 +417,18 @@ class LinkValidateUrlLight extends DrupalWebTestCase { $this->assertEqual(FALSE, $valid, 'newsgroup names can\'t contain underscores, so it should come back as invalid.'); } - function testValidateInternalLink() { - $valid = link_validate_url('node/5'); - $this->assertEqual(LINK_INTERNAL, $valid, 'Test normal internal link.'); - } - - function testValidateInternalLinkWithDot() { - $valid = link_validate_url('rss.xml'); - $this->assertEqual(LINK_INTERNAL, $valid, 'Test rss.xml internal link.'); - } - - function testValidateInternalLinkToFile() { - $valid = link_validate_url('files/test.jpg'); - $this->assertEqual(LINK_INTERNAL, $valid, 'Test files/test.jpg internal link.'); + function testValidateInternalLinks() { + $links = array( + 'node/5', + 'rss.xml', + 'files/test.jpg', + '/var/www/test', + ); + + foreach ($links as $link) { + $valid = link_validate_url($link); + $this->assertEqual(LINK_INTERNAL, $valid, 'Test ' . $link . ' internal link.'); + } } function testValidateExternalLinks() { @@ -481,15 +470,20 @@ class LinkValidateUrlLight extends DrupalWebTestCase { //$valid2 = valid_url($link, TRUE); //$this->assertEqual(TRUE, $valid2, "Using valid_url() on $link."); } + // Test if we can make a tld valid: + variable_set('link_extra_domains', array('frog')); + $valid = link_validate_url('http://www.example.frog'); + $this->assertEqual(LINK_EXTERNAL, $valid, "Testing that http://www.example.frog is a valid external link if we've added 'frog' to the list of valid domains."); } function testInvalidExternalLinks() { $links = array( 'http://www.ex ample.com/', - '//www.example.com/', 'http://25.0.0/', // bad ip! 'http://4827.0.0.2/', + '//www.example.com/', 'http://www.testß.com/', // ß not allowed in domain names! + 'http://www.example.frog/', // Bad TLD //'http://www.-fudge.com/', // domains can't have sections starting with a dash. ); foreach ($links as $link) { @@ -497,5 +491,4 @@ class LinkValidateUrlLight extends DrupalWebTestCase { $this->assertEqual(FALSE, $valid, 'Testing that '. $link .' is not a valid link.'); } } - } diff --git a/sites/all/modules/link/views/link_views_handler_argument_target.inc b/sites/all/modules/link/views/link_views_handler_argument_target.inc index b0a2a3ee48118e7c18e049f6f7d14beccd9782fc..3a6fdf013b6fefe883402c3da1c159902326cb26 100644 --- a/sites/all/modules/link/views/link_views_handler_argument_target.inc +++ b/sites/all/modules/link/views/link_views_handler_argument_target.inc @@ -93,7 +93,7 @@ class link_views_handler_argument_target extends views_handler_argument { '#default_value' => $this->options['validate_type'], ); - $validate_types = array('none' => t('<Basic validation>')); + $validate_types = array('none' => t('- Basic validation -')); $plugins = views_fetch_plugin_data('argument validator'); foreach ($plugins as $id => $info) { $valid = TRUE; @@ -140,10 +140,10 @@ class link_views_handler_argument_target extends views_handler_argument { * * The argument sent may be found at $this->argument. */ - function query() { + function query($group_by = FALSE) { $this->ensure_my_table(); // Because attributes are stored serialized, our only option is to also // serialize the data we're searching for and use LIKE to find similar data. - $this->query->add_where(0, $this->table_alias .'.'. $this->real_field ." LIKE '%%%s%'", serialize(array('target' => $this->argument))); + $this->query->add_where(0, $this->table_alias . '.' . $this->real_field . " LIKE '%%%s%'", serialize(array('target' => $this->argument))); } } diff --git a/sites/all/modules/link/views/link_views_handler_filter_protocol.inc b/sites/all/modules/link/views/link_views_handler_filter_protocol.inc index f43e345c4b2e5790b33e6cfc83fb5a92ce83cc70..9d194aa94bbc8f9eebf67b8b345bc753ec9a4571 100644 --- a/sites/all/modules/link/views/link_views_handler_filter_protocol.inc +++ b/sites/all/modules/link/views/link_views_handler_filter_protocol.inc @@ -80,27 +80,28 @@ class link_views_handler_filter_protocol extends views_handler_filter_string { $where_conditions = array(); foreach ($protocols as $protocol) { // Simple case, the URL begins with the specified protocol. - $condition = $field .' LIKE \''. $protocol .'%\''; + $condition = $field . ' LIKE \'' . $protocol . '%\''; // More complex case, no protocol specified but is automatically cleaned up // by link_cleanup_url(). RegEx is required for this search operation. if ($protocol == 'http') { + $LINK_DOMAINS = _link_domains(); if ($db_type == 'pgsql') { // PostGreSQL code has NOT been tested. Please report any problems to the link issue queue. // pgSQL requires all slashes to be double escaped in regular expressions. // See http://www.postgresql.org/docs/8.1/static/functions-matching.html#FUNCTIONS-POSIX-REGEXP - $condition .= ' OR '. $field .' ~* \''.'^(([a-z0-9]([a-z0-9\\-_]*\\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))'.'\''; + $condition .= ' OR ' . $field .' ~* \''.'^(([a-z0-9]([a-z0-9\\-_]*\\.)+)(' . $LINK_DOMAINS . '|[a-z][a-z]))' . '\''; } else { // mySQL requires backslashes to be double (triple?) escaped within character classes. // See http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html#operator_regexp - $condition .= ' OR '. $field .' REGEXP \''.'^(([a-z0-9]([a-z0-9\\\-_]*\.)+)('. LINK_DOMAINS .'|[a-z][a-z]))'.'\''; + $condition .= ' OR ' . $field . ' REGEXP \''.'^(([a-z0-9]([a-z0-9\\\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z][a-z]))' . '\''; } } $where_conditions[] = $condition; } - $this->query->add_where($this->options['group'], implode(' '. $this->operator .' ', $where_conditions)); + $this->query->add_where($this->options['group'], implode(' ' . $this->operator . ' ', $where_conditions)); } } diff --git a/sites/all/modules/metatag/CHANGELOG.txt b/sites/all/modules/metatag/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..aeba62f52d1bbcf5a469cfb8d308b3c48b29ad3f --- /dev/null +++ b/sites/all/modules/metatag/CHANGELOG.txt @@ -0,0 +1,180 @@ +Metatag 7.x-1.0-beta5, 2013-03-23 +--------------------------------- +#1844638 by DamienMcKenna: Updated help messages around update 7004, when ran + via Drush it will no longer used Batch API. +#1844764 by Devin Carlson, DamienMcKenna: Fix arg placeholders in t() calls. +#1846516 by Staratel: Incorrect arguments for watchdog(). +#1846516 by DamienMcKenna: Further incorrect arguments for watchdog(). +#1844638 by DamienMcKenna: Correctly used drupal_is_cli() instead of just + php_sapi_name(). +#1846978 by edulterado: Corrected the theme function name used with the + Twitter Cards submodule. +#1307804 by juampy: Support for Select_or_Other for use with the OpenGraph + 'type' field. +#1854522 by DamienMcKenna: Redundant return statements in the MetaTag classes. +#1852600 by DamienMcKenna: Only use the first page argument in the Views and + Panels preprocessors if it is numerical. +#1850014 by plopesc: Not all contexts that may be shown on the admin page will + have a path condition defined. +#1846080 by DamienMcKenna: Only support entities that have the 'metatags' + option specifically enabled. +#1857116 by DamienMcKenna: Purge {metatag} records for a few known unsupported + entities that old versions would have saved. +#1857116 by DamienMcKenna: Don't purge 'file' {metatag} records until #1857334 + is decided. +#1857360 by DamienMcKenna: Purge {metatag} records for nodes, taxonomy terms + and users that were purged but where the APIs of older versions failed to + remove them. +#1857116 by DamienMcKenna: Purge {metatag} records for Profile2. +#1852600 by helmo: Typo in Views integration function. +#1852022 by DamienMcKenna: Don't export the {metatag_config}.cid field. +#1862570 by DamienMcKenna: Purge any empty values that may have been added by + very early releases. +#1862570 by DamienMcKenna: Follow-up to correctly handle the serialized empty + array. +#1864340 by cdoyle, DamienMcKenna: Incorrect output for certain Twitter Card + tags. +#1865170 by DamienMcKenna: Fix metatag_requirements() return array when the + Page Title module is also installed. +#1722564 by DamienMcKenna: Provide a hook_requirements() message and README.txt + note about a possible conflict with the Exclude Node Title module. +#1284756 by damiankloip, sylus, alanburke, lancee: Migrate module integration. +#1865228 by greggles, DamienMcKenna: Added the rel=author link meta tag. +#1866122 by DamienMcKenna: Added the twitter:site:id and twitter:creator:id + meta tags. +#1866980 by makangus: Corrected metatag_features_revert(). +#1862818 by DYdave, DamienMcKenna: Added documentation for + hook_metatag_config_default(). +#1778534 by DamienMcKenna: Added the original-source meta tag. +#1886170 by DamienMcKenna: Typo in the API docs regarding enabling metatag + support in custom entities. +#1871020 by DamienMcKenna: Compatibility problem with Workbench_Moderation. +#1773926 by Dave Reid: Fixed token validation fails on config edit if the + instance context is not an entity type. +#1814736 by plach, Dave Reid: metatag_page_build() did not check if the + global:frontpage metatag configuration is disabled. +#1871852: Fixed metatag_update_7005() did not check if the watchdog table + exists. +#1891082 by bago, Dave Reid: Fixed metatag_config_instance_label() failed to + recurse properly. +#1915284: Fixed metatag_html_head_alter() stopped removing duplicate tags too + soon. Fixed duplicate canonical links when global redirect is enabled. +#1845326 by DamienMcKenna, Peacog: Resolved language handling problems to + correctly identify the langcode to properly work with or without + Entity_Translation. +#1876042 by DamienMcKenna: Rename variables to use $entity_id instead of $id + and $entity_type instead of $type. +#1859136 by splatio, DamienMcKenna, multpix: Feeds integration - allow meta tag + fields to be the target for data imported using the Feeds module. +#1880302 by olli, DamienMcKenna: Resolve problems with Features integration. +#1923030 by krlucas, DamienMcKenna: Only run metatag_entity_update() on + supported entities. +#1844638 by DamienMcKenna, mikeytown2: Remove unnecessary duplicate {metatag} + records, fix language values for all entities. +#1935084 by DamienMcKenna: Remove unnecessary items from metatag_hook_info() + that was causing problems with PHP 5.4. +#1791720 by kbasarab: Added the news_keywords meta tag. +#1934492 by juampy, DamienMcKenna: Added a page for reverting meta tags for + specific entity or bundle. +#1386320: Note a known issue of using custom template files that do not output + the $page['content'] variable. +#1917902 by DamienMcKenna: Ensure strings returned from token replacement of + text fields ([node:summary]) is passed through the appropriate text filters. +#1919070 by DamienMcKenna: Fix any records that may have been corrupted by e.g. + #1871020. +#1861656 by DamienMcKenna, torrance123: Optionally load the global meta tags on + all pages, enabled by default. +#1871798 by mstrelan: Clear the Context plugin cache when metatag_context is + enabled so that the new plugin becomes available. +#1932192 by DamienMcKenna: Only run metatag_entity_view() once per page view. +#1900434 by Dustin Currie, j0rd, DamienMcKenna: Added several new OpenGraph meta + tags, including ones for videos, location and contact information. +#1883118 by DamienMcKenna: Improve the help message on Metatag:Context's Path + field as neither relative nor absolute URLs will work. +#1945114 by SergO, DamienMcKenna: A query from #1919070 was missing the + preproccess wrapper around the table name. +#1908586 by DamienMcKenna: Added a line to README.txt explaining how to + customize the tokens used to generate the meta tags. +#1350610 by DamienMcKenna: metatag_update_7001 needed to drop the primary key + before customizing it. +#1859136 by DamienMcKenna: Fixed scenarios when updating an entity there are two + copies of the data submitted, e.g. Feeds integration. +#1308790 by DamienMcKenna: Documented that [current-user] tokens should not be + used. +#1318294 by DamienMcKenna: Documented how to use Imagecache Token to resize + images that are being used as tokens for meta tags. +#1871534 by DamienMcKenna: Documented how some browser plugins can make the page + title appear to be wrapped with doublequotes though the output doesn't + actually show them. + + +Metatag 7.x-1.0-beta4, 2012-11-17 +--------------------------------- +#1842764 by DamienMcKenna: Work around problems in metatag_entity_load() + stemming from an outdated database schema, leave a message suggesting the + site admin run the database updates. +#1842868 by DamienMcKenna: Changed metatag_update_7003 to automatically assign + the correct language per entity, added update_7004 to fix records updated in + beta3, fixed the language selection for loading meta tags so sites without + translation functionality continue to work correctly. +#1842868 by DamienMcKenna: Changed update 7003 again so it *only* adds the new + field, changed update 7004 so it will update all records using Batch API. +#1843676 by DamienMcKenna: Changed the hook_requirements message to an INFO + message if Page_Title is also installed, will freak people out less. + + +Metatag 7.x-1.0-beta3, 2012-11-16 +--------------------------------- +#1688286 by colan, DamienMcKenna: Support for Entity Translation. +#1835030 by DamienMcKenna: Documentation and hook_requirements note re Drupal + core v7.17. +#1840402 by DamienMcKenna, paperdhc: Corrected use of array_pop(). +#1841404 by mh86: Don't attempt to load meta tags for unsupported entities, and + don't support configuration-only entities. +#1841564 by peximo: Correctly identify the content language being used on the + homepage. +#1841774 by DamienMcKenna: Provide a warning via hook_requirements if the Page + Title module is also enabled, due to the possibilities of complications and + unexpected results. +#1363476 by DamienMcKenna: Workaround to trigger metatag_entity_view() if the + current CTools (Panels, Panelizer, etc) page is an entity display page. +#1842052 by DamienMcKenna: Don't process unsupported entities being displayed + via Views. +#1664322 by nico059, kerasai, miechiel, idflood, DamienMcKenna, alexweber: + Twitter Cards meta tags. +#1842198 by DamienMcKenna: Move the 'advanced' fieldset under the others. +#1840236 by weri, Marty2081: Only revert the requested feature, not all + features. + + +Metatag 7.x-1.0-beta2, 2012-10-30 +--------------------------------- +#1817580 by DamienMcKenna: Removed code that was enabling debug mode on all + Contexts. +#1818240 by DamienMcKenna: Added $instance value to the drupal_alter() call in + metatag_metatags_view(). +#1817984 by DamienMcKenna, alexweber: Documented + hook_metatag_metatags_view_alter(). +#1818252 by DamienMcKenna: There was no caching on the front page's meta tags. +#1818516 by DamienMcKenna: Incorrect variable check in metatag_page_build(). +#1818762 by DamienMcKenna: Updated hook_hook_info(). +#1466292 by DamienMcKenna: Listed hooks in metatag.api.php and everywhere the + hooks are triggered there's a comment to say what the hook is. +#1818984 by DamienMcKenna: Add the $instance value to metatag_context's + triggering of hook_metatag_metatags_view. +#1819000 by DamienMcKenna: Don't load default meta tags if no active contexts + define meta tags. +#1819448 by DamienMcKenna: Error on admin page if any meta tags were disabled. +#1818958 by DamienMcKenna: The $cid_parts array should contain all relevant + entity variables. +#1820362 by DamienMcKenna: $cid_parts should use base_path() instead of '/'. +#1820374 by DamienMcKenna: Front page $cid_parts did not include the full URL. +#1822726 by DamienMcKenna: Ensure the CTools exportables system is loaded. +#1818300 by eugene.ilyin, DamienMcKenna: Improved Features integration. +#1151936 by DamienMcKenna, maximpodorov: Workaround to trigger + metatag_entity_view() if the current Views page is an entity display page. + + +Metatag 7.x-1.0-beta1, 2012-10-19 +--------------------------------- +First mostly-stable release. diff --git a/sites/all/modules/metatag/README.txt b/sites/all/modules/metatag/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..5e2f5a76f6554db5d4f6ca0cf72ab22552c8d99a --- /dev/null +++ b/sites/all/modules/metatag/README.txt @@ -0,0 +1,162 @@ +Metatag +------- +This module allows you to automatically provide structured metadata, aka "meta +tags", about your website and web pages. + +In the context of search engine optimization, providing an extensive set of +meta tags may help improve your site's & pages' ranking, thus may aid with +achieving a more prominent display of your content within search engine +results. Additionally, using meta tags can help control the summary content +that is used within social networks when visitors link to your site, +particularly the Open Graph submodule for use with Facebook (see below). + +This version of the module only works with Drupal 7.15 and newer. + + +Features +------------------------------------------------------------------------------ +The primary features include: + +* The current supported basic meta tags are ABSTRACT, DESCRIPTION, CANONICAL, + COPYRIGHT, GENERATOR, IMAGE_SRC, KEYWORDS, PUBLISHER, ROBOTS, SHORTLINK and + the page's TITLE tag. + +* Multi-lingual support using the Entity Translation module. + +* Per-path control over meta tags using the "Meta tags: Context" submodule + (requires the Context module). + +* The fifteen Dublin Core Basic Element Set 1.1 meta tags may be added by + enabling the "Meta tags: Dublin Core" submodule. + +* The Open Graph Protocol meta tags, as used by Facebook, may be added by + enabling the "Meta tags: Open Graph" submodule. + +* The Twitter Cards meta tags may be added by enabling the "Meta tags: Twitter + Cards" submodule. + +* An API allowing for additional meta tags to be added, beyond what is provided + by this module - see metatag.api.php for full details. + + +Configuration +------------------------------------------------------------------------------ + 1. On the People Permissions administration page ("Administer >> People + >> Permissions") you need to assign: + + - The "Administer meta tags" permission to the roles that are allowed to + access the meta tags admin pages to control the site defaults. + + - The "Edit meta tags" permission to the roles that are allowed to change + meta tags on each individual page (node, term, etc). + + 2. The main admininistrative page controls the site-wide defaults, both global + settings and defaults per entity (node, term, etc), in addition to those + assigned specifically for the front page: + admin/config/search/metatags + + 3. Each supported entity object (nodes, terms, users) will have a set of meta + tag fields available for customization on their respective edit page, these + will inherit their values from the defaults assigned in #2 above. Any + values that are not overridden per object will automatically update should + the defaults be updated. + + 4. As the meta tags are output using Tokens, it may be necessary to customize + the token display for the site's entities (content types, vocabularies, + etc). To do this go to e.g. admin/structure/types/manage/article/display, in + the "Custom Display Settings" section ensure that "Tokens" is checked (save + the form if necessary), then to customize the tokens go to: + admin/structure/types/manage/article/display/token + + +Fine Tuning +------------------------------------------------------------------------------ +* By default Metatag will load the global default values for all pages that do + not have meta tags assigned via the normal entity display or via Metatag + Context. This may be disabled by setting the variable 'metatag_load_all_pages' + to FALSE through one of the following methods: + * Use Drush to set the value: + drush vset metatag_load_all_pages FALSE + * Hardcode the value in the site's settings.php file: + $conf['metatag_load_all_pages'] = FALSE; + To re-enable this option simply set the value to TRUE. + + +Developers +------------------------------------------------------------------------------ +Full API documentation is available in metatag.api.php. + +To enable Metatag support in custom entities, add 'metatag' => TRUE to either +the entity or bundle definition in hook_entity_info(); see metatag.api.php for +further details and example code. + + +Troubleshooting / Known Issues +------------------------------------------------------------------------------ +* When using custom page template files, e.g. page--front.tpl.php, it is + important to ensure that the following code is present in the template file: + <?php render($page['content']); ?> + or + <?php render($page['content']['metatags']); ?> + Without one of these being present the meta tags will not be displayed. +* Versions of Drupal older than v7.17 were missing necessary functionality for + taxonomy term pages to work correctly. +* Using Metatag with values assigned for the page title and the Page Title + module simultaneously can cause conflicts and unexpected results. +* Using the Exclude Node Title module will cause the [node:title] token to be + empty on node pages, so using [current-page:title] will work around the + issue. Note: it isn't possible to "fix" this as it's a by-product of what + Exclude Node Title does - it removes the node title from display. +* When customizing the meta tags for user pages, it is strongly recommended to + not use the [current-user] tokens, these pertain to the person *viewing* the + page and not e.g. the person who authored a page. +* If images being displayed in image tags need to be resized to fit a specific + requirements, use the Imagecache Token module to customize the value. +* Certain browser plugins, e.g. on Chrome, can cause the page title so be + displayed with additional doublequotes, e.g. instead of: + <title>The page title | My cool site</title> + it will show: + <title>"The page title | My cool site"</title> + The solution is to remove the browser plugin - the page's actual output is not + affected, it is just a problem in the browser. + + +Related modules +------------------------------------------------------------------------------ +Some modules are available that extend Metatag with additional functionality: + +* Domain Meta Tags + http://drupal.org/project/domain_meta + Integrates with the Domain Access module, so each site of a multi-domain + install can separately control their meta tags. + +* Select or Other + http://drupal.org/project/select_or_other + Enhances the user experience of the metatag_opengraph submodule by allowing + the creation of custom Open Graph types. + +* Imagecache Token + http://drupal.org/project/imagecache_token + Provide tokens to load fields using an image style preset, for when meta tags + need to fix exact requirements. + + +Credits / Contact +------------------------------------------------------------------------------ +Currently maintained by Dave Reid [1] and Damien McKenna [2]. + +All initial development was sponsored by Acquia [3] and Palantir [4]; +continued development sponsored by Palantir and Mediacurrent [5]. + +The best way to contact the authors is to submit an issue, be it a support +request, a feature request or a bug report, in the project issue queue: + http://drupal.org/project/issues/metatag + + +References +------------------------------------------------------------------------------ +1: http://drupal.org/user/53892 +2: http://drupal.org/user/108450 +3: http://www.acquia.com/ +4: http://www.palantir.net/ +5: http://www.mediacurrent.com/ diff --git a/sites/all/modules/metatag/metatag.admin.inc b/sites/all/modules/metatag/metatag.admin.inc index d7873807b3ff465631179c80fbda711524c0104b..0b4278a4772bcd170f9acef4826dc14142848436 100644 --- a/sites/all/modules/metatag/metatag.admin.inc +++ b/sites/all/modules/metatag/metatag.admin.inc @@ -89,6 +89,10 @@ function metatag_config_overview() { // Add a summary of the configuration's defaults. $summary = array(); foreach ($config->config as $metatag => $data) { + // Skip meta tags that were disabled. + if (empty($metatags[$metatag])) { + continue; + } $summary[] = array( check_plain($metatags[$metatag]['label']) . ':', check_plain(metatag_get_value($metatag, $data, array('raw' => TRUE))), @@ -173,7 +177,6 @@ function metatag_config_overview() { ), '#rows' => $rows, '#empty' => t('No meta tag defaults available yet.'), - '#caption' => '<div class="js-show">' . t('To view a summary of the default meta tags and the inheritance, click on a meta tag type.') . '</div>', '#attributes' => array( 'class' => array('metatag-config-overview'), ), @@ -270,7 +273,14 @@ function metatag_config_edit_form($form, &$form_state, $config) { $contexts = explode(':', $config->instance); $options['context'] = $contexts[0]; if ($contexts[0] != 'global') { - $options['token types'] = array(token_get_entity_mapping('entity', $contexts[0])); + // The context part of the instance may not map to an entity type, so allow + // the token_get_entity_mapping() function to fallback to the provided type. + if ($token_type = token_get_entity_mapping('entity', $contexts[0], TRUE)) { + $options['token types'] = array($token_type); + } + else { + $options['token types'] = array($contexts[0]); + } } // Ensure that this configuration is properly compared to its parent 'default' @@ -382,3 +392,141 @@ function metatag_config_export_form($config) { ctools_include('export'); return drupal_get_form('ctools_export_form', ctools_export_crud_export('metatag_config', $config), t('Export')); } + +/** + * Form constructor to revert nodes to their default metatags. + * + * @see metatag_bulk_revert_form_submit() + * @ingroup forms + */ +function metatag_bulk_revert_form() { + // Get the list of entity:bundle options + $options = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + foreach (array_keys($entity_info['bundles']) as $bundle) { + if (metatag_entity_supports_metatags($entity_type, $bundle)) { + $options[$entity_type . ':' . $bundle] = + $entity_info['label'] . ': ' . $entity_info['bundles'][$bundle]['label']; + } + } + } + + $form['update'] = array( + '#type' => 'checkboxes', + '#required' => TRUE, + '#title' => t('Select the entities to revert'), + '#options' => $options, + '#default_value' => array(), + '#description' => t('All meta tags will be removed for all content of the selected entities.'), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Revert'), + ); + + return $form; +} + +/** + * Form submit handler for metatag reset bulk revert form. + * + * @see metatag_batch_revert_form() + * @see metatag_bulk_revert_batch_finished() + */ +function metatag_bulk_revert_form_submit($form, &$form_state) { + $batch = array( + 'title' => t('Bulk updating metatags'), + 'operations' => array(), + 'finished' => 'metatag_bulk_revert_batch_finished', + 'file' => drupal_get_path('module', 'metatag') . '/metatag.admin.inc', + ); + + // Set a batch operation per entity:bundle. + foreach (array_filter($form_state['values']['update']) as $option) { + list($entity_type, $bundle) = explode(':', $option); + $batch['operations'][] = array('metatag_bulk_revert_batch_operation', array($entity_type, $bundle)); + } + + batch_set($batch); +} + +/** + * Batch callback: delete custom metatags for selected bundles. + */ +function metatag_bulk_revert_batch_operation($entity_type, $bundle, &$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + // Query the selected entity table. + $entity_info = entity_get_info($entity_type); + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $entity_type) + ->propertyCondition($entity_info['entity keys']['id'], $context['sandbox']['current'], '>') + ->propertyOrderBy($entity_info['entity keys']['id']); + if ($entity_type != 'user') { + /** + * Entities which do not define a bundle such as User fail returning no results. + * @see http://drupal.org/node/1054168#comment-5266208 + */ + $query->entityCondition('bundle', $bundle); + } + // Get the total amount of entities to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->count()->execute(); + $query->count = FALSE; + + // If there are no bundles to revert, stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + // Process 25 entities per iteration. + $query->range(0, 25); + $result = $query->execute(); + $ids = !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array(); + foreach ($ids as $id) { + $metatags = metatag_metatags_load($entity_type, $id); + if (!empty($metatags)) { + db_delete('metatag')->condition('entity_type', $entity_type) + ->condition('entity_id', $id) + ->execute(); + metatag_metatags_cache_clear($entity_type, $id); + $context['results'][] = t('Reverted metatags for @bundle with id @id.', array( + '@bundle' => $entity_type . ': ' . $bundle, + '@id' => $id, + )); + } + } + + $context['sandbox']['count'] += count($ids); + $context['sandbox']['current'] = max($ids); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} + +/** + * Batch finished callback. + */ +function metatag_bulk_revert_batch_finished($success, $results, $operations) { + if ($success) { + if (!count($results)) { + drupal_set_message(t('No metatags were reverted.')); + } + else { + $message = theme('item_list', array('items' => $results)); + drupal_set_message($message); + } + } + else { + $error_operation = reset($operations); + drupal_set_message(t('An error occurred while processing @operation with arguments : @args', + array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE)))); + } +} diff --git a/sites/all/modules/metatag/metatag.admin.js b/sites/all/modules/metatag/metatag.admin.js index f278226f686bdce97874cec2d972ca200885b988..c213f721d0587385fa1a7cc3203ee05554ca46d5 100644 --- a/sites/all/modules/metatag/metatag.admin.js +++ b/sites/all/modules/metatag/metatag.admin.js @@ -5,8 +5,16 @@ Drupal.behaviors.metatagUIConfigListing = { // Hide elements to be visible if JavaScript is enabled. $('.js-show').show(); + // Make the leaf arrow clickable. + $('.metatag-config-label').hover(function(){ + $(this).css({'cursor':'pointer'}); + }) + .click(function(){ + $(this).find('a.toggle-details', context).trigger('click'); + }); + // Show or hide the summary - $('table.metatag-config-overview a.toggle-details', context).click(function() { + $('table.metatag-config-overview a.toggle-details', context).click(function(event) { $(this).parent('div').siblings('div.metatag-config-details').each(function() { if ($(this).hasClass('js-hide')) { $(this).slideDown('slow').removeClass('js-hide'); @@ -23,6 +31,10 @@ Drupal.behaviors.metatagUIConfigListing = { else { $(this).parent('div').removeClass('expanded').addClass('collapsed'); } + + // This event may be triggered by a parent element click - so we don't + // want the click to bubble up otherwise we get recursive click events. + event.stopPropagation(); }); } } diff --git a/sites/all/modules/metatag/metatag.api.php b/sites/all/modules/metatag/metatag.api.php index b1245a0355d86abd6b95987b8e96c16fe8400f33..b2d97ddf8df0e1300cd1297015b3e303b9d18cab 100644 --- a/sites/all/modules/metatag/metatag.api.php +++ b/sites/all/modules/metatag/metatag.api.php @@ -3,3 +3,217 @@ * @file * API documentation for the Metatag module. */ + +/** + * To enable Metatag support in custom entities, add 'metatags' => TRUE to the + * entity definition in hook_entity_info(), e.g.: + * + * /** + * * Implements hook_entity_info(). + * * + * * Taken from the Examples module. + * * / + * function entity_example_entity_info() { + * $info['entity_example_basic'] = array( + * 'label' => t('Example Basic Entity'), + * 'controller class' => 'EntityExampleBasicController', + * 'base table' => 'entity_example_basic', + * 'uri callback' => 'entity_example_basic_uri', + * 'fieldable' => TRUE, + * 'metatags' => TRUE, + * 'entity keys' => array( + * 'id' => 'basic_id' , // The 'id' (basic_id here) is the unique id. + * 'bundle' => 'bundle_type' // Bundle will be determined by the 'bundle_type' field + * ), + * 'bundle keys' => array( + * 'bundle' => 'bundle_type', + * ), + * 'static cache' => TRUE, + * 'bundles' => array( + * 'first_example_bundle' => array( + * 'label' => 'First example bundle', + * 'admin' => array( + * 'path' => 'admin/structure/entity_example_basic/manage', + * 'access arguments' => array('administer entity_example_basic entities'), + * ), + * ), + * ), + * 'view modes' => array( + * 'tweaky' => array( + * 'label' => t('Tweaky'), + * 'custom settings' => FALSE, + * ), + * ) + * ); + * + * return $info; + * } + * + * The definition of each bundle may be handled separately, thus support may be + * disabled for the entity as a whole but enabled for individual bundles. This + * is handled via the 'metatags' value on the bundle definition, e.g.: + * + * 'bundles' => array( + * 'first_example_bundle' => array( + * 'label' => 'First example bundle', + * 'metatags' => TRUE, + * 'admin' => array( + * 'path' => 'admin/structure/entity_example_basic/manage', + * 'access arguments' => array('administer entity_example_basic entities'), + * ), + * ), + * ), + */ + +/** + * Provides a default configuration for Metatag intances. + * + * This hook allows modules to provide their own Metatag instances which can + * either be used as-is or as a "starter" for users to build from. + * + * This hook should be placed in MODULENAME.metatag.inc and it will be auto- + * loaded. MODULENAME.metatag.inc *must* be in the same directory as the + * .module file which *must* also contain an implementation of + * hook_ctools_plugin_api, preferably with the same code as found in + * metatag_ctools_plugin_api(). + * + * The $config->disabled boolean attribute indicates whether the Metatag + * instance should be enabled (FALSE) or disabled (TRUE) by default. + * + * @return + * An associative array containing the structures of Metatag instances, as + * generated from the Export tab, keyed by the Metatag config name. + * + * @see metatag_metatag_config_default() + * @see metatag_ctools_plugin_api() + */ +function hook_metatag_config_default() { + $configs = array(); + + $config = new stdClass(); + $config->instance = 'config1'; + $config->api_version = 1; + $config->disabled = FALSE; + $config->config = array( + 'title' => array('value' => '[current-page:title] | [site:name]'), + 'generator' => array('value' => 'Drupal 7 (http://drupal.org)'), + 'canonical' => array('value' => '[current-page:url:absolute]'), + 'shortlink' => array('value' => '[current-page:url:unaliased]'), + ); + $configs[$config->instance] = $config; + + $config = new stdClass(); + $config->instance = 'config2'; + $config->api_version = 1; + $config->disabled = FALSE; + $config->config = array( + 'title' => array('value' => '[user:name] | [site:name]'), + ); + $configs[$config->instance] = $config; + + return $configs; +} + +/** + * + */ +function hook_metatag_config_default_alter(&$config) { +} + +/** + * + */ +function hook_metatag_config_delete($entity_type, $entity_ids) { +} + +/** + * + */ +function hook_metatag_config_insert($config) { +} + +/** + * + */ +function hook_metatag_config_instance_info() { + return array(); +} + +/** + * + */ +function hook_metatag_config_instance_info_alter(&$info) { +} + +/** + * + */ +function hook_metatag_config_load() { +} + +/** + * + */ +function hook_metatag_config_load_presave() { +} + +/** + * + */ +function hook_metatag_config_presave($config) { +} + +/** + * + */ +function hook_metatag_config_update($config) { +} + +/** + * + */ +function hook_metatag_info() { + return array(); +} + +/** + * + */ +function hook_metatag_info_alter(&$info) { +} + +/** + * + */ +function hook_metatag_load_entity_from_path_alter(&$path, $result) { +} + +/** + * Alter metatags before being cached. + * + * This hook is invoked prior to the meta tags for a given page are cached. + * + * @param array $output + * All of the meta tags to be output for this page in their raw format. This + * is a heavily nested array. + * @param string $instance + * An identifier for the current page's page type, typically a combination + * of the entity name and bundle name, e.g. "node:story". + */ +function hook_metatag_metatags_view_alter(&$output, $instance) { + if (isset($output['description']['#attached']['drupal_add_html_head'][0][0]['#value'])) { + $output['description']['#attached']['drupal_add_html_head'][0][0]['#value'] = 'O rly?'; + } +} + +/** + * + */ +function hook_metatag_page_cache_cid_parts_alter(&$cid_parts) { +} + +/** + * + */ +function hook_metatag_presave(&$metatags, $entity_type, $entity_id) { +} diff --git a/sites/all/modules/metatag/metatag.features.inc b/sites/all/modules/metatag/metatag.features.inc new file mode 100644 index 0000000000000000000000000000000000000000..5f8ab5440c3410294d91ba43cdb29159a7d2b07e --- /dev/null +++ b/sites/all/modules/metatag/metatag.features.inc @@ -0,0 +1,87 @@ +<?php +/** + * @file + * Features integration for the Metatag module. + */ + +/** + * Implements hook_features_export(). + */ +function metatag_features_export($data, &$export, $module_name = '', $type = 'metatag') { + $pipe = array(); + + foreach ($data as $name) { + if (metatag_config_load($name)) { + $export['features'][$type][$name] = $name; + } + } + + $export['dependencies']['metatag'] = 'metatag'; + + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function metatag_features_export_render($module_name, $data, $export = NULL) { + $code = array(); + $code[] = ' $config = array();'; + $code[] = ''; + + foreach ($data as $key => $name) { + if (is_object($name)) { + $name = $name->instance; + } + if ($config = metatag_config_load($name)) { + $export = new stdClass(); + $export->instance = $config->instance; + $export->config = $config->config; + $export = features_var_export($export, ' '); + $key = features_var_export($name); + $code[] = " // Exported Metatag config instance: {$name}."; + $code[] = " \$config[{$key}] = {$export};"; + $code[] = ""; + } + } + $code[] = ' return $config;'; + $code = implode("\n", $code); + return array('metatag_export_default' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function metatag_features_revert($module) { + if ($feature_conf = features_get_default('metatag', $module)) { + foreach (array_keys($feature_conf) as $config) { + if ($conf = metatag_config_load($config)) { + db_delete('metatag_config')->condition('instance', $config)->execute(); + } + unset($feature_conf[$config]['cid']); + $object = new stdClass(); + $object->cid = NULL; + $object->instance = $config; + $object->config = $feature_conf[$config]['config']; + metatag_config_save($object); + } + } +} + +/** + * Implements hook_features_export_options(). + */ +function metatag_features_export_options() { + $instances = metatag_config_instance_info(); + foreach ($instances as $key => $instance) { + $options[$key] = $key; + }; + return $options; +} + +/** + * Implements hook_features_rebuild(). + */ +function metatag_features_rebuild($module) { + metatag_features_revert($module); +} diff --git a/sites/all/modules/metatag/metatag.feeds.inc b/sites/all/modules/metatag/metatag.feeds.inc new file mode 100644 index 0000000000000000000000000000000000000000..3ee0acfa010fe5b7a7fa0d795c1dc1b78b16bc72 --- /dev/null +++ b/sites/all/modules/metatag/metatag.feeds.inc @@ -0,0 +1,37 @@ +<?php +/** + * @file + * Feeds mapping implementation for the Metatag module. + */ + +/** + * Implements hook_feeds_processor_targets_alter(). + */ +function metatag_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { + if (metatag_entity_supports_metatags($entity_type)) { + $info = metatag_get_info(); + foreach ($info['tags'] as $name => $tag) { + $targets['meta_' . $name] = array( + 'name' => 'Meta tag: ' . check_plain($tag['label']), + 'callback' => 'metatag_feeds_set_target', + 'description' => $tag['description'], + ); + } + } +} + +/** + * Callback function to set value of a metatag tag. + */ +function metatag_feeds_set_target($source, $entity, $target, $value) { + // Don't do anything if we weren't given any data. + if (empty($value)) { + return; + } + + // Strip the prefix that was added above. + $name = str_replace('meta_', '', $target); + + // Assign the value. + $entity->metatags[$name]['value'] = $value; +} diff --git a/sites/all/modules/metatag/metatag.inc b/sites/all/modules/metatag/metatag.inc index f079d2971e999cb78a3efc71a0a99daccf712970..d2f9aac94799508a2dfd5d1f1f128dc363f9d139 100644 --- a/sites/all/modules/metatag/metatag.inc +++ b/sites/all/modules/metatag/metatag.inc @@ -65,7 +65,6 @@ class DrupalDefaultMetaTag implements DrupalMetaTagInterface { return array( '#attached' => array('drupal_add_html_head' => array(array($element, $element['#id']))), ); - return $element; } } @@ -88,7 +87,7 @@ class DrupalTextMetaTag extends DrupalDefaultMetaTag { '#default_value' => isset($this->data['value']) ? $this->data['value'] : '', '#element_validate' => array('token_element_validate'), '#token_types' => $options['token types'], - '#maxlength' => 255, + '#maxlength' => 1024, ); return $form; @@ -98,7 +97,7 @@ class DrupalTextMetaTag extends DrupalDefaultMetaTag { $options += array( 'token data' => array(), 'clear' => TRUE, - 'sanitize' => FALSE, + 'sanitize' => TRUE, 'raw' => FALSE, ); @@ -142,7 +141,6 @@ class DrupalLinkMetaTag extends DrupalTextMetaTag { return array( '#attached' => array('drupal_add_html_head' => array(array($element, $element['#id']))), ); - return $element; } } diff --git a/sites/all/modules/metatag/metatag.info b/sites/all/modules/metatag/metatag.info index 0addb2e115d62b86a279c9850e6af4659a6a788d..4a61acd75db34ac577d8726a5c34d289eb67928f 100644 --- a/sites/all/modules/metatag/metatag.info +++ b/sites/all/modules/metatag/metatag.info @@ -2,16 +2,23 @@ name = Meta tags description = "Adds support and an API to implement meta tags." package = Meta tags core = 7.x -dependencies[] = token + +# This requires Drupal 7.15 or newer. +dependencies[] = system (>=7.15) + +# CTools and Token are also required. dependencies[] = ctools +dependencies[] = token + configure = admin/config/search/metatags files[] = metatag.inc +files[] = metatag.migrate.inc files[] = metatag.test -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/metatag.install b/sites/all/modules/metatag/metatag.install index 47518f91205c34086fc9f3b5437dfa2740c35762..f1ee370dffb12affacce441e767938fbb3b5da69 100644 --- a/sites/all/modules/metatag/metatag.install +++ b/sites/all/modules/metatag/metatag.install @@ -32,6 +32,7 @@ function metatag_schema() { 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The primary identifier for a metatag configuration set.', + 'no export' => TRUE, ), 'instance' => array( 'type' => 'varchar', @@ -78,8 +79,15 @@ function metatag_schema() { 'not null' => TRUE, 'serialize' => TRUE, ), + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language of the tag.', + ), ), - 'primary key' => array('entity_type', 'entity_id'), + 'primary key' => array('entity_type', 'entity_id', 'language'), ); $schema['cache_metatag'] = drupal_get_schema_unprocessed('system', 'cache'); @@ -88,6 +96,103 @@ function metatag_schema() { return $schema; } +/** + * Implements hook_requirements(). + */ +function metatag_requirements($phase) { + $requirements = array(); + // Ensure translations don't break during installation. + $t = get_t(); + + if ($phase == 'runtime') { + // Work out the release of D7 that is currently running. + list($major, $minor) = explode('.', VERSION); + // Strip off any suffixes on the version string, e.g. "17-dev". + if (strpos('-', $minor)) { + list($minor, $suffix) = explode('-', $minor); + } + + // Releases of Drupal older than 7.15 did not have entity_language(), which + // is now required. + if ($minor < 15) { + $requirements['metatag'] = array( + 'severity' => REQUIREMENT_WARNING, + 'title' => 'Metatag', + 'value' => $t('Upgrade Drupal core to v7.15 or newer'), + 'description' => $t("This older version of Drupal core is missing functionality necessary for the module's multilingual support, it must be upgraded to at least version 7.15."), + ); + } + // Releases of Drupal older than 7.17 did not trigger hook_entity_view on + // term pages, so recommend updating. + elseif ($minor < 17) { + $requirements['metatag'] = array( + 'severity' => REQUIREMENT_WARNING, + 'title' => 'Metatag', + 'value' => $t('Upgrade Drupal core to v7.17 or newer'), + 'description' => $t('Your older version of Drupal core is missing functionality necessary for taxonomy term pages to work correctly, it is strongly recommended to upgrade to the latest release.'), + ); + } + // Everything's OK. + else { + $requirements['metatag'] = array( + 'severity' => REQUIREMENT_OK, + 'title' => 'Metatag', + 'value' => $t('Drupal core is compatible'), + 'description' => $t('Older versions of Drupal core were missing functionality necessary for taxonomy term pages to work correctly, but this version <em>will</em> work correctly.'), + ); + } + + // Add a note if Page Title is also installed. + if (module_exists('page_title')) { + $requirements['metatag_page_title'] = array( + 'severity' => REQUIREMENT_INFO, + 'title' => 'Metatag', + 'value' => $t('Possible conflicts with Page Title module'), + 'description' => $t('The Metatag module is able to customize page titles so running the Page Title module simultaneously can lead to complications.'), + ); + } + + // Add a note if Page Title is also installed. + if (module_exists('exclude_node_title')) { + $requirements['metatag_exclude_node_title'] = array( + 'severity' => REQUIREMENT_INFO, + 'title' => 'Metatag', + 'value' => $t('Possible conflicts with Exclude Node Title module'), + 'description' => $t('The Metatag module\'s default setitngs for content types (nodes) uses [node:title] for the page title. Unfortunately, Exclude Node Title hides this so the page title ends up blank. It is recommended to <a href="!config">change the "title" field\'s default value</a> to "[current-page:title]" instead of "[node:title]" for any content types affected by Exclude Node Title.', array('!config' => 'admin/config/search/metatags')), + ); + } + } + + return $requirements; +} + +/** + * Implements hook_install(). + */ +// function metatag_install() { +// } + +/** + * Implements hook_uninstall(). + */ +function metatag_uninstall() { + // This variable is created via hook_enable. + variable_del('metatag_schema_installed'); +} + +/** + * Implements hook_enable(). + */ +function metatag_enable() { + variable_set('metatag_schema_installed', TRUE); +} + +/** + * Implements hook_disable(). + */ +// function metatag_disable() { +// } + /** * Disable the deprecated metatag_ui module which has been merged into metatag. */ @@ -99,14 +204,528 @@ function metatag_update_7000() { } /** - * Fix the {metatag_config}.cid column cannot be NULL. + * Fix the "{metatag_config}.cid column cannot be NULL" error. */ function metatag_update_7001() { - $field = array( + $table_name = 'metatag_config'; + $field_name = 'cid'; + $field_spec = array( 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The primary identifier for a metatag configuration set.', ); - db_change_field('metatag_config', 'cid', 'cid', $field); + $keys = array('primary key' => array($field_name)); + + // Before making any changes, drop the existing primary key. + db_drop_primary_key($table_name); + + // Rejig the field, and turn on the primary key again. + db_change_field($table_name, $field_name, $field_name, $field_spec, $keys); +} + +/** + * Disable the deprecated metatag_ui module which has been merged into metatag, + * again. + */ +function metatag_update_7002() { + if (module_exists('metatag_ui')) { + module_disable(array('metatag_ui'), FALSE); + drupal_uninstall_modules(array('metatag_ui'), FALSE); + drupal_set_message(t('The deprecated Metatag UI module has been disabled.')); + } +} + +/** + * Add the {metatag}.language field. + */ +function metatag_update_7003() { + // Set the target table and field name. + $table_name = 'metatag'; + $field_name = 'language'; + + // Don't add the new field if it already exists. + if (!db_field_exists($table_name, $field_name)) { + // Describe the new field. + $field_spec = array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language of the tag', + ); + + // Add it and update the primary key. + db_add_field($table_name, $field_name, $field_spec); + db_drop_primary_key($table_name); + db_add_primary_key($table_name, array('entity_type', 'entity_id', 'language')); + } +} + +/** + * Replaced by updates 7009, 7010, 7011, 7012 and 7013. + */ +function metatag_update_7004() { + // Do nothing. +} + +/** + * Removing wrong metatag watchdog entries that break the admin/reports/dblog + * page. + */ +function metatag_update_7005() { + if (db_table_exists('watchdog')) { + db_delete('watchdog') + ->condition('type', 'metatag') + ->condition('variables', serialize('info')) + ->execute(); + } +} + +/** + * Remove {metatag} records that were added by old versions of the module for + * entities that don't actually support meta tags. A more complete version of + * this will be added later on after it's (hopefully) guaranteed that all + * modules have updated to the correct API usage. + */ +function metatag_update_7006() { + $entity_types = array( + // Core. + 'comment', + 'menu_link', + 'taxonomy_vocabulary', + // Some contrib entities. + 'mailchimp_list', + 'profile2', + 'profile2_type', + 'redirect', + 'rules_config', + 'wysiwyg_profile', + ); + foreach ($entity_types as $entity_type) { + $num_deleted = db_delete('metatag') + ->condition('entity_type', $entity_type) + ->execute(); + if ($num_deleted > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for the @type entity type, it does not support meta tags.', array('@count' => $num_deleted, '@type' => $entity_type))); + } + } +} + +/** + * Remove {metatag} records for objects that have been deleted; older versions + * of Metatag may have failed to purge these. + */ +function metatag_update_7007() { + $result = db_query("DELETE m + FROM {metatag} m + LEFT OUTER JOIN {node} n + ON m.entity_id=n.nid + WHERE m.entity_type='node' + AND n.nid IS NULL"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for nodes that had been purged.', array('@count' => $result->rowCount()))); + } + + $result = db_query("DELETE m + FROM {metatag} m + LEFT OUTER JOIN {users} u + ON m.entity_id=u.uid + WHERE m.entity_type='user' + AND u.uid IS NULL"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for users that had been purged.', array('@count' => $result->rowCount()))); + } + + $result = db_query("DELETE m + FROM {metatag} m + LEFT OUTER JOIN {taxonomy_term_data} t + ON m.entity_id=t.tid + WHERE m.entity_type='taxonomy_term' + AND t.tid IS NULL"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Removed @count meta tag record(s) for taxonomy terms that had been purged.', array('@count' => $result->rowCount()))); + } +} + +/** + * Remove any empty records that may be hanging around from old releases. + */ +function metatag_update_7008() { + $result = db_query("DELETE m FROM {metatag} m WHERE m.data IS NULL or m.data = '' OR m.data = :empty", array(':empty' => serialize(array()))); + if ($result->rowCount() > 0) { + drupal_set_message(t('Purged @count empty meta tag record(s).', array('@count' => $result->rowCount()))); + } +} + +/** + * Fix {metatag} records for taxonomy terms. + */ +function metatag_update_7009() { + // Remove duplicates. + _metatag_remove_dupes('taxonomy_term'); + + // The taxonomy term entity doesn't support a 'language' option, so reset it + // to LANGUAGE_NONE. + $result = db_query("UPDATE {metatag} SET language = :language WHERE entity_type='taxonomy_term'", array(':language' => LANGUAGE_NONE)); + if ($result->rowCount() > 0) { + drupal_set_message(t('Fixed language values for @count taxonomy terms.', array('@count' => $result->rowCount()))); + } +} + +/** + * Fix {metatag} records for users. + */ +function metatag_update_7010() { + // Remove duplicates. + _metatag_remove_dupes('user'); + + // Update User values. + $result = db_query("UPDATE {metatag} SET language = :language WHERE entity_type='user'", array(':language' => LANGUAGE_NONE)); + if ($result->rowCount() > 0) { + drupal_set_message(t('Fixed language values for @count user records.', array('@count' => $result->rowCount()))); + } +} + +/** + * Fix {metatag} records for nodes. + */ +function metatag_update_7011() { + // Only proceed if Entity_Translation is not enabled as it allows each node + // record to have multiple languages available. + if (module_exists('entity_translation')) { + drupal_set_message(t("Entity Translation is enabled, so node meta tags will not be updated, to avoid accidental dataloss.")); + return; + } + + // Remove duplicates. + _metatag_remove_dupes('node'); + + // Update Node values. + $result = db_query("UPDATE {metatag} AS m INNER JOIN {node} n ON m.entity_id=n.nid AND m.entity_type='node' SET m.language = n.language"); + if ($result->rowCount() > 0) { + drupal_set_message(t('Fixed language values for @count nodes.', array('@count' => $result->rowCount()))); + } +} + +/** + * Remove duplicate {metatag} records for non-core entities. + */ +function metatag_update_7012() { + if (module_exists('entity_translation')) { + drupal_set_message(t("Entity Translation is enabled, duplicate meta tags will not be removed for custom entities, to avoid accidental dataloss.")); + return; + } + + $records = db_select('metatag', 'm') + ->fields('m', array('entity_type')) + ->condition('m.entity_type', array('node', 'taxonomy_term', 'user'), 'NOT IN') + ->orderBy('m.entity_type', 'ASC') + ->orderBy('m.entity_id', 'ASC') + ->distinct() + ->execute(); + + $entity_types = array(); + foreach ($records as $record) { + $entity_types[] = $record->entity_type; + // Remove duplicates. + _metatag_remove_dupes($record->entity_type); + } + + if (empty($entity_types)) { + drupal_set_message(t('There were no other records to fix.')); + } +} + +/** + * Fix the {metatag} language value for all non-core entity records. This might + * take a while, depending on how much data needs to be converted. + */ +function metatag_update_7013(&$sandbox) { + if (module_exists('entity_translation')) { + drupal_set_message(t("Entity Translation is enabled, meta tags will not be updated for custom entities, to avoid accidental dataloss.")); + return; + } + + // Use the sandbox at your convenience to store the information needed + // to track progression between successive calls to the function. + if (!isset($sandbox['progress'])) { + // The count of records visited so far. + $sandbox['progress'] = 0; + + // Because the {metatag} table uses multiple primary keys, there's no easy + // way to do this, so we're going to cache all record keys and manually + // step through them. + $records = db_select('metatag', 'm') + ->fields('m', array('entity_type', 'entity_id')) + ->condition('m.entity_type', array('node', 'taxonomy_term', 'user'), 'NOT IN') + ->orderBy('m.entity_type', 'ASC') + ->orderBy('m.entity_id', 'ASC') + ->execute(); + $sandbox['records'] = array(); + foreach ($records as $record) { + $sandbox['records'][] = $record; + } + + // If there's no data, don't bother with the extra work. + if (empty($sandbox['records'])) { + watchdog('metatag', 'Update 7013: No meta tag records need updating.', array(), WATCHDOG_INFO); + if (drupal_is_cli()) { + drupal_set_message(t('Update 7013: No meta tag records need updating.')); + } + return t('No meta tag records need updating.'); + } + + // Total records that must be visited. + $sandbox['max'] = count($sandbox['records']); + + // A place to store messages during the run. + $sandbox['messages'] = array(); + + // An initial record of the number of records to be updated. + watchdog('metatag', 'Update 7013: !count records to update.', array('!count' => $sandbox['max']), WATCHDOG_INFO); + if (drupal_is_cli()) { + drupal_set_message(t('Update 7013: !count records to update.', array('!count' => $sandbox['max']))); + } + + // Last record processed. + $sandbox['current_record'] = -1; + } + + // Process records by groups of 10 (arbitrary value). + // When a group is processed, the batch update engine determines whether it + // should continue processing in the same request or provide progress + // feedback to the user and wait for the next request. + $limit = 10; + + // The for loop will run as normal when ran via update.php, but when ran via + // Drush it'll just run 'til it's finished. + $increment = 1; + if (drupal_is_cli()) { + $increment = 0; + } + + // Set default values. + for ($ctr = 0; $ctr < $limit; $ctr += $increment) { + $sandbox['current_record']++; + if (empty($sandbox['records'][$sandbox['current_record']])) { + break; + } + + // Shortcuts for later. + $entity_type = $sandbox['records'][$sandbox['current_record']]->entity_type; + $entity_id = $sandbox['records'][$sandbox['current_record']]->entity_id; + + // Load the entity. + $entities = entity_load($entity_type, array($entity_id)); + if (!empty($entities)) { + $entity = array_pop($entities); + + // Make sure that the entity has a language set. + if (!empty($entity)) { + // If there's a (non-empty) language value, use it. + $new_language = entity_language($entity_type, $entity); + if (empty($new_language)) { + $new_language = LANGUAGE_NONE; + } + // Update the 'language' value. + db_update('metatag') + ->fields(array('language' => $new_language)) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute(); + } + } + + // Update our progress information. + $sandbox['progress']++; + } + + // Set the "finished" status, to tell batch engine whether this function + // needs to run again. If you set a float, this will indicate the progress of + // the batch so the progress bar will update. + $sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']); + + if ($sandbox['#finished']) { + // Clear all caches so the fixed data will be reloaded. + cache_clear_all('*', 'cache_metatag', TRUE); + + // A final log of the number of records that were converted. + watchdog('metatag', 'Update 7013: !count records were updated in total.', array('!count' => $sandbox['progress']), WATCHDOG_INFO); + if (drupal_is_cli()) { + drupal_set_message(t('Update 7013: !count records were updated.', array('!count' => $sandbox['progress']))); + } + + // hook_update_N() may optionally return a string which will be displayed + // to the user. + return t('!count records were updated in total.', array('!count' => $sandbox['progress'])); + } +} + +/** + * Remove duplicate records for a given entity. + * + * It should be OK to run this without doing a separate batch process as there + * shouldn't be many records that have this problem. Hopefully. + * + * @param $entity_type + * The name of an entity type to check for. + */ +function _metatag_remove_dupes($entity_type) { + $purge_count = 0; + + // First step: fix the records. There should not be multiple records for the + // same entity_id with different languages. + $dupe_records = db_query("SELECT m.entity_id, count(m.language) AS the_count + FROM {metatag} m + WHERE + m.entity_type = :type + GROUP BY m.entity_id + HAVING the_count > 1", array(':type' => $entity_type)); + + if (!empty($dupe_records)) { + foreach ($dupe_records as $record) { + $entity_id = $record->entity_id; + $langs = db_query("SELECT m.entity_id, m.language, m.data FROM {metatag} m WHERE m.entity_type = :type AND m.entity_id = :id", array(':type' => $entity_type, ':id' => $entity_id))->fetchAll(); + + // Work out which language record to remove. Will need to store this as + // an array incase there are multiple records to purge. + $langs_to_remove = array(); + + // Check for duplicate records. + // Outer loop starts from the beginning. + for ($outer = 0; $outer < count($langs); $outer++) { + // This record may have been removed already. + if (isset($langs[$outer])) { + // Inner loop starts from the end. + for ($inner = count($langs) - 1; $inner > 0; $inner--) { + // Work out if the outer loop's data is the same as the inner + // loop's. + if (isset($langs[$inner]) && $langs[$outer]->data == $langs[$inner]->data) { + // Remove the second record. + $langs_to_remove[] = $langs[$inner]->language; + unset($langs[$inner]); + } + } + } + } + + // Only one record left. + if (count($langs) == 1) { + // This is how it should be, this record is fine. + } + // More than one record, work out which one to keep. + elseif (count($langs) > 1) { + // Work out the entity's language. + $entity = entity_load($entity_type, $entity_id); + $entity_language = entity_language($entity_type, $entity); + if (empty($language)) { + $entity_language = LANGUAGE_NONE; + } + + // Work out if the entity's language record exists. + $lang_pos = NULL; + foreach ($langs as $key => $record) { + if ($record->language == $entity_language) { + $lang_pos = $key; + break; + } + } + // If the language record exists, delete the others. + if (isset($lang_pos)) { + foreach ($langs as $key => $record) { + if ($record->language != $entity_language) { + $langs_to_remove[] = $record->language; + } + } + } + // Otherwise look for a record for the site's default language. + else { + foreach ($langs as $key => $record) { + if ($record->language == $GLOBALS['language']->language) { + $lang_pos = $key; + break; + } + } + if (isset($lang_pos)) { + foreach ($langs as $key => $record) { + if ($record->language != $GLOBALS['language']->language) { + $langs_to_remove[] = $record->language; + } + } + } + // Finally check for LANGUAGE_NONE. + else { + foreach ($langs as $key => $record) { + if ($record->language == LANGUAGE_NONE) { + $lang_pos = $key; + break; + } + } + if (isset($lang_pos)) { + foreach ($langs as $key => $record) { + if ($record->language != LANGUAGE_NONE) { + $langs_to_remove[] = $record->language; + } + } + } + } + } + } + + // Purge the redundant records. + if (!empty($langs_to_remove)) { + $purge_count += db_delete('metatag') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('language', $langs_to_remove) + ->execute(); + } + } + } + + if (empty($purge_count)) { + drupal_set_message(t('No duplicate :entity_type records were found (this is a good thing).', array(':entity_type' => $entity_type))); + watchdog('metatag', 'No duplicate :entity_type records were found (this is a good thing).', array(':entity_type' => $entity_type)); + } + else { + drupal_set_message(t('Purged :count duplicate :entity_type record(s).', array(':count' => $purge_count, ':entity_type' => $entity_type))); + watchdog('metatag', 'Purged :count duplicate :entity_type record(s).', array(':count' => $purge_count, ':entity_type' => $entity_type)); + return; + } +} + +/** + * Fix {metatag} records that may have been corrupted by #1871020. + */ +function metatag_update_7014() { + $records = db_query("SELECT * + FROM {metatag} m + WHERE + m.`data` LIKE :nolang + OR m.`data` LIKE :lang + OR m.`data` LIKE :und", + array( + ':nolang' => 'a:1:{s:0:"";a:%:{s:%;a:%:{%;}}}', + ':lang' => 'a:1:{s:2:"__";a:%:{s:%;a:%:{%;}}}', + ':und' => 'a:1:{s:3:"___";a:%:{s:%;a:%:{%;}}}', + )); + + // Nothing to fix. + if ($records->rowCount() == 0) { + drupal_set_message(t('No corrupt records to fix, this is good news :-)')); + } + + // Fix the faulty records. + else { + foreach ($records as $record) { + // Extract the data and get the first element of the array, this should be + // valid data. + $record->data = reset(unserialize($record->data)); + + // Update the record. + drupal_write_record('metatag', $record, array('entity_type', 'entity_id', 'language')); + } + drupal_set_message(t('Fixed @count corrupt meta tag record(s).', array('@count' => $records->rowCount()))); + } } diff --git a/sites/all/modules/metatag/metatag.metatag.inc b/sites/all/modules/metatag/metatag.metatag.inc index 51f0c99e5521d7ff4e32b388411b7fdc33ac18ea..bd892556305708268244a9df8a487924a3743555 100644 --- a/sites/all/modules/metatag/metatag.metatag.inc +++ b/sites/all/modules/metatag/metatag.metatag.inc @@ -13,6 +13,8 @@ function metatag_metatag_config_default() { $config->config = array( 'title' => array('value' => '[current-page:title] | [site:name]'), 'generator' => array('value' => 'Drupal 7 (http://drupal.org)'), + 'canonical' => array('value' => '[current-page:url:absolute]'), + 'shortlink' => array('value' => '[current-page:url:unaliased]'), ); $configs[$config->instance] = $config; @@ -23,6 +25,7 @@ function metatag_metatag_config_default() { $config->config = array( 'title' => array('value' => variable_get('site_slogan') ? '[site:name] | [site:slogan]' : '[site:name]'), 'canonical' => array('value' => '[site:url]'), + 'shortlink' => array('value' => '[site:url]'), ); $configs[$config->instance] = $config; @@ -97,13 +100,30 @@ function metatag_metatag_info() { $info['groups']['advanced'] = array( 'label' => t('Advanced'), 'form' => array( - '#weight' => 90, + '#weight' => 100, ), ); + $info['tags']['title'] = array( + 'label' => t('Page title'), + 'description' => t("The text to display in the title bar of a visitor's web browser when they view this page. This meta tag may also be used as the title of the page when a visitor bookmarks or favorites this page."), + 'class' => 'DrupalTitleMetaTag', + ); + $info['tags']['description'] = array( 'label' => t('Description'), - 'description' => t("A brief and concise summary of the page's content, preferrably 150 characters or less. The description meta tag may be used by search engines to display a snippet about the page in search results."), + 'description' => t("A brief and concise summary of the page's content, preferably 150 characters or less. The description meta tag may be used by search engines to display a snippet about the page in search results."), + 'class' => 'DrupalTextMetaTag', + 'form' => array( + '#type' => 'textarea', + '#rows' => 2, + '#wysiwyg' => FALSE, + ), + ); + + $info['tags']['abstract'] = array( + 'label' => t('Abstract'), + 'description' => t("A brief and concise summary of the page's content, preferably 150 characters or less. The abstract meta tag may be used by search engines for archiving purposes."), 'class' => 'DrupalTextMetaTag', 'form' => array( '#type' => 'textarea', @@ -111,16 +131,12 @@ function metatag_metatag_info() { '#wysiwyg' => FALSE, ), ); + $info['tags']['keywords'] = array( 'label' => t('Keywords'), - 'description' => t("A comma-separated list of keywords about the page. This meta tag is <em>not</em> supported by most search engines."), + 'description' => t("A comma-separated list of keywords about the page. This meta tag is <em>not</em> used by most search engines."), 'class' => 'DrupalTextMetaTag', ); - $info['tags']['title'] = array( - 'label' => t('Title'), - 'description' => t("The text to display in the title bar of a visitor's web browser when they view this page. This meta tag may also be used as the title of the page when a visitor bookmarks or favorites this page."), - 'class' => 'DrupalTitleMetaTag', - ); // More advanced meta tags. $info['tags']['robots'] = array( @@ -129,15 +145,26 @@ function metatag_metatag_info() { 'class' => 'DrupalListMetaTag', 'form' => array( '#options' => array( + 'index' => t('Allow search engines to index this page (assumed).'), + 'follow' => t('Allow search engines to follow links on this page (assumed).'), 'noindex' => t('Prevent search engines from indexing this page.'), 'nofollow' => t('Prevent search engines from following links on this page.'), 'noarchive' => t('Prevent a cached copy of this page from being available in the search results.'), 'nosnippet' => t('Prevents a description from appearing below the page in the search results, as well as prevents caching of the page.'), 'noodp' => t('Blocks the <a href="@odp-url">Open Directory Project</a> description of the page from being used in the description that appears below the page in the search results.', array('@odp-url' => 'http://www.dmoz.org/')), + 'noydir' => t('Prevents Yahoo! from listing this page in the <a href="@ydir">Yahoo! Directory</a>.', array('@ydir' => 'http://dir.yahoo.com/')), ), ), 'group' => 'advanced', ); + + $info['tags']['news_keywords'] = array( + 'label' => t('Google News Keywords'), + 'description' => t('A comma-separated list of keywords about the page. This meta tag is used as an indicator in <a href="@google_news">Google News</a>.', array('@google_news' => 'http://support.google.com/news/publisher/bin/answer.py?hl=en&answer=68297')), + 'class' => 'DrupalTextMetaTag', + 'group' => 'advanced', + ); + $info['tags']['generator'] = array( 'label' => t('Generator'), 'description' => t("Describes the name and version number of the software or publishing tool used to create the page."), @@ -146,6 +173,7 @@ function metatag_metatag_info() { 'context' => array('global'), 'group' => 'advanced', ); + $info['tags']['copyright'] = array( 'label' => t('Copyright'), 'description' => t("Details a copyright, trademark, patent, or other information that pertains to intellectual property about this page. Note that this will not automatically protect your site's content or your intellectual property."), @@ -153,19 +181,27 @@ function metatag_metatag_info() { 'group' => 'advanced', ); - // Link tags. + $info['tags']['image_src'] = array( + 'label' => t('Image'), + 'description' => t("An image associated with this page, for use as a thumbnail in social networks and other services."), + 'class' => 'DrupalLinkMetaTag', + 'group' => 'advanced', + ); + $info['tags']['canonical'] = array( 'label' => t('Canonical URL'), 'description' => t("Tells search engines where the preferred location or URL is for this page to help eliminate self-created duplicate content for search engines."), 'class' => 'DrupalLinkMetaTag', 'group' => 'advanced', ); + $info['tags']['shortlink'] = array( 'label' => t('Shortlink URL'), 'description' => '', 'class' => 'DrupalLinkMetaTag', 'group' => 'advanced', ); + $info['tags']['publisher'] = array( 'label' => t('Publisher URL'), 'description' => '', @@ -173,5 +209,20 @@ function metatag_metatag_info() { 'group' => 'advanced', ); + $info['tags']['author'] = array( + 'label' => t('Author URL'), + 'description' => "Used by some search engines to confirm authorship of the content on a page. Should be either the full URL for the author's Google+ profile page or a local page with information about the author.", + 'class' => 'DrupalLinkMetaTag', + 'group' => 'advanced', + ); + + $info['tags']['original-source'] = array( + 'label' => t('Original Source'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'advanced', + 'description' => "Used to indicate the URL that broke the story, and can link to either an internal URL or an external source. If the full URL is not known it is acceptable to use a partial URL or just the domain name.", + ); + return $info; } diff --git a/sites/all/modules/metatag/metatag.migrate.inc b/sites/all/modules/metatag/metatag.migrate.inc new file mode 100644 index 0000000000000000000000000000000000000000..b189ef001f48f4803be31ef0200bca5d269aebf0 --- /dev/null +++ b/sites/all/modules/metatag/metatag.migrate.inc @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Metatag support for migrate. + */ + +/** + * Implements hook_migrate_api(). + */ +function metatag_migrate_api() { + return array('api' => 2); +} + +/** + * Metatags destination handler. + */ +class MigrateMetatagHandler extends MigrateDestinationHandler { + + public function __construct() { + $this->registerTypes(array('node', 'user', 'taxonomy_term')); + } + + /** + * Implements MigrateDestinationHandler::fields(). + */ + public function fields() { + $fields = array(); + $elements = metatag_get_info(); + + foreach ($elements['tags'] as $value) { + $metatag_field = 'metatag_' . $value['name']; + $fields[$metatag_field] = $value['description']; + } + + return $fields; + } + + /** + * Implements MigrateDestinationHandler::prepare(). + */ + public function prepare($entity, stdClass $row) { + $elements = metatag_get_info(); + + foreach ($elements['tags'] as $value) { + $metatag_field = 'metatag_' . $value['name']; + if (isset($entity->$metatag_field)) { + $entity->metatags[$value['name']]['value'] = $entity->$metatag_field; + unset($entity->$metatag_field); + } + } + } +} diff --git a/sites/all/modules/metatag/metatag.module b/sites/all/modules/metatag/metatag.module index 9899f5e4e96c08864f818981d2b99839cdc65d18..6f0a4933e2e78b9eac94cfd6318b91b6899d4a0b 100644 --- a/sites/all/modules/metatag/metatag.module +++ b/sites/all/modules/metatag/metatag.module @@ -2,9 +2,24 @@ /** * @todo Add revisionable support for metatag data. - * @todo Add multilingual support for metatag data - is this even needed? */ +/** + * Implements hook_help(). + */ +function metatag_help($path, $arg) { + if ($path == 'admin/config/search/metatags') { + return '<p>' . t('To view a summary of the default meta tags and the inheritance, click on a meta tag type.') . '</p>'; + } + elseif ($path == 'admin/help#metatag') { + return '<p>' . t('The Meta tags module provides a options to let each page have customized meta data added to the "meta" tags in the HEAD section of the document.') . '</p>'; + } + elseif ($path == 'admin/config/search/metatags/bulk-revert') { + return '<p>' . t('This form <strong>will wipe out</strong> all custom meta tags for the selected entities, reverting them to the default configuration assigned at the <a href="@url">Defaults tab</a>. For example, if the meta tags are changed for an article they will be removed if the "Node: Article" checkbox is selected.', array('@url' => url('admin/config/search/metatags'))) . '</p>'; + } + +} + /** * Implements hook_theme(). */ @@ -43,38 +58,34 @@ function metatag_ctools_plugin_api($owner, $api) { */ function metatag_hook_info() { $hooks = array( - 'metatag_info', - 'metatag_info_alter', + 'metatag_config_default', + 'metatag_config_default_alter', + 'metatag_config_delete', + 'metatag_config_insert', 'metatag_config_instance_info', 'metatag_config_instance_info_alter', 'metatag_config_load', - 'metatag_config_presave', - 'metatag_config_insert', + 'metatag_config_load_presave', 'metatag_config_update', - 'metatag_config_delete', - 'metatag_load', - 'metatag_insert', - 'metatag_update', - 'metatag_delete', - 'metatag_alter', - 'metatag_config_default', - 'metatag_config_default_alter', - 'metatag_api', + 'metatag_info', + 'metatag_info_alter', ); return array_fill_keys($hooks, array('group' => 'metatag')); } /** - * Implements hook_permisson(). + * Implements hook_permission(). */ function metatag_permission() { $permissions['administer meta tags'] = array( 'title' => t('Administer meta tags.'), 'restrict access' => TRUE, + 'description' => t('Control the main settings pages and modify per-object meta tags.'), ); $permissions['edit meta tags'] = array( - 'title' => t('Edit meta tags.'), + 'title' => t('Edit meta tags'), + 'description' => t('Modify meta tags on individual entity records (nodes, terms, users, etc).'), ); return $permissions; } @@ -158,6 +169,15 @@ function metatag_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => 10, ); + $items['admin/config/search/metatags/bulk-revert'] = array( + 'title' => 'Bulk revert', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_bulk_revert_form'), + 'access arguments' => array('administer meta tags'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 30, + 'file' => 'metatag.admin.inc', + ); return $items; } @@ -179,18 +199,18 @@ function metatag_flush_caches() { * The levels of defaults is arranged by splitting the $instance variable by * the colon character, and always using a 'global' instance at the end. */ -function metatag_config_load_with_defaults($instance) { +function metatag_config_load_with_defaults($instance, $include_global = TRUE) { $defaults = &drupal_static(__FUNCTION__, array()); - // Check to see if - if (!isset($defaults[$instance])) { - $cid = "config:{$instance}"; + // Statically cache defaults since they can include multiple levels. + $cid = "config:{$instance}" . ($include_global ? ':withglobal' : ':withoutglobal'); + if (!isset($defaults[$cid])) { if ($cache = cache_get($cid, 'cache_metatag')) { - $defaults[$instance] = $cache->data; + $defaults[$cid] = $cache->data; } else { - $defaults[$instance] = array(); - $instances = metatag_config_get_parent_instances($instance); + $defaults[$cid] = array(); + $instances = metatag_config_get_parent_instances($instance, $include_global); $configs = metatag_config_load_multiple($instances); foreach ($instances as $key) { // Ignore disabled configurations. @@ -199,14 +219,14 @@ function metatag_config_load_with_defaults($instance) { } // Add config to the defaults array. - $defaults[$instance] += $configs[$key]->config; + $defaults[$cid] += $configs[$key]->config; } - cache_set($cid, $defaults[$instance], 'cache_metatag'); + cache_set($cid, $defaults[$cid], 'cache_metatag'); } } - return $defaults[$instance]; + return $defaults[$cid]; } /** @@ -231,15 +251,22 @@ function metatag_config_load_multiple(array $instances) { function metatag_config_save($config) { $config->is_new = empty($config->cid); - // Allow modules to alter the configuration before it is saved. + // Allow modules to alter the configuration before it is saved using + // hook_metatag_config_presave(). module_invoke_all('metatag_config_presave', $config); if ($config->is_new) { drupal_write_record('metatag_config', $config); + + // Allow modules to act upon the record insertion using + // hook_metatag_config_insert(). module_invoke_all('metatag_config_insert', $config); } else { drupal_write_record('metatag_config', $config, array('cid')); + + // Allow modules to act upon the record update using + // hook_metatag_config_insert(). module_invoke_all('metatag_config_update', $config); } @@ -267,62 +294,134 @@ function metatag_config_delete($instance) { function metatag_config_cache_clear() { cache_clear_all('*', 'cache_metatag', TRUE); drupal_static_reset('metatag_config_load_with_defaults'); + drupal_static_reset('metatag_entity_has_metatags'); + drupal_static_reset('metatag_entity_supports_metatags'); ctools_include('export'); ctools_export_load_object_reset('metatag_config'); } -function metatag_metatags_load($type, $id) { - $metatags = metatag_metatags_load_multiple($type, array($id)); +/** + * Load an entity's tags. + * + * @param $entity_type + * The entity type to load + * @param $entity_id + * The ID of the entity to load + * @return + * An array of tag data keyed by language. + */ +function metatag_metatags_load($entity_type, $entity_id) { + $metatags = metatag_metatags_load_multiple($entity_type, array($entity_id)); return !empty($metatags) ? reset($metatags) : array(); } -function metatag_metatags_load_multiple($type, array $ids) { +/** + * Load tags for multiple entities. + * + * @param $entity_type + * The entity type to load + * @param $entity_ids + * The list of entity IDs + * @return + * An array of tag data, keyed by ID. + */ +function metatag_metatags_load_multiple($entity_type, array $entity_ids) { // Double check entity IDs are numeric thanks to Entity API module. - $ids = array_filter($ids, 'is_numeric'); - if (empty($ids)) { + $entity_ids = array_filter($entity_ids, 'is_numeric'); + if (empty($entity_ids)) { return array(); } // Also need to check if the metatag table exists since this condition could // fire before the table has been installed yet. - if (!db_table_exists('metatag')) { - return array(); + if (!variable_get('metatag_schema_installed', FALSE)) { + if (db_table_exists('metatag')) { + variable_set('metatag_schema_installed', TRUE); + } + else { + watchdog('metatag', 'The system tried to load metatag data before the schema was fully loaded.', array(), WATCHDOG_WARNING); + return array(); + } + } + + // Get all translations of tag data for this entity. + $result = db_query("SELECT entity_id, data, language FROM {metatag} WHERE (entity_type = :type) AND (entity_id IN (:ids))", array( + ':type' => $entity_type, + ':ids' => $entity_ids, + )); + + // Marshal it into an array keyed by entity ID. Each value is an array of + // translations keyed by language code. + $metatags = array(); + while ($record = $result->fetchObject()) { + $metatags[$record->entity_id][$record->language] = unserialize($record->data); } - $metatags = db_query("SELECT entity_id, data FROM {metatag} WHERE entity_type = :type AND entity_id IN (:ids)", array( - ':type' => $type, - ':ids' => $ids, - ))->fetchAllKeyed(); - $metatags = array_map('unserialize', $metatags); return $metatags; } -function metatag_metatags_save($type, $id, $metatags) { - // Check that $id is numeric because of Entity API and string IDs. - if (!is_numeric($id)) { +/** + * Save an entity's tags. + * + * @param $entity_type + * The entity type to load + * @param $entity_id + * The entity's ID + * @param $metatags + * All of the tag information + * @param $language + * The language of the translation set + */ +function metatag_metatags_save($entity_type, $entity_id, $metatags, $language) { + // If no language assigned, use the has-no-language language. + if (!$language) { + $language = LANGUAGE_NONE; + } + + // Check that $entity_id is numeric because of Entity API and string IDs. + if (!is_numeric($entity_id)) { return; } - // Allow other modules to alter the metatags prior to saving. + // Certain modules, e.g. Workbench Moderation, will cause the data to be in + // an unsupported format; the problem needs to be resolved elsewhere so this + // can only be considered a temporary fix. + // TODO: Solve the core problem, which will probably entail something + // similar to http://drupal.org/node/1876034. + if (isset($metatags[$language])) { + // There are certain occasions when the old data and the new data are + // *both* added to the $metatags array, in this case throw away the language + // data. + $lang_data = $metatags[$language]; + unset($metatags[$language]); + if (empty($metatags)) { + $metatags = $lang_data; + } + } + + // Allow other modules to alter the meta tags prior to saving using + // hook_metatag_presave(). foreach (module_implements('metatag_presave') as $module) { $function = "{$module}_metatag_presave"; - $function($metatags, $type, $id); + $function($metatags, $entity_type, $entity_id, $language); } if (empty($metatags)) { // If the data array is empty, there is no data to actually save, so // just delete the record from the database. db_delete('metatag') - ->condition('entity_type', $type) - ->condition('entity_id', $id) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('language', $language) ->execute(); } else { // Otherwise save the data for this entity. db_merge('metatag') ->key(array( - 'entity_type' => $type, - 'entity_id' => $id, + 'entity_type' => $entity_type, + 'entity_id' => $entity_id, + 'language' => $language, )) ->fields(array( 'data' => serialize($metatags), @@ -331,30 +430,61 @@ function metatag_metatags_save($type, $id, $metatags) { } // Clear cached data. - metatag_metatags_cache_clear($type, $id); + metatag_metatags_cache_clear($entity_type, $entity_id); } -function metatag_metatags_delete($type, $id) { - return metatag_metatags_delete_multiple($type, array($id)); +/** + * Delete an entity's tags. + * + * @param $entity_type + * The entity type + * @param $entity_id + * The entity's ID + * @param $langcode + * The language ID of the entry to delete. If left blank, all language + * entries for this entity will be deleted. + */ +function metatag_metatags_delete($entity_type, $entity_id, $langcode = NULL) { + return metatag_metatags_delete_multiple($entity_type, array($entity_id), $langcode); } -function metatag_metatags_delete_multiple($type, array $ids) { +/** + * Delete multiple entities' tags. + * + * @param $entity_type + * The entity type + * @param $entity_ids + * The list of IDs + * @param $langcode + * The language ID of the entities to delete. If left blank, all language + * entries for the enities will be deleted. + */ +function metatag_metatags_delete_multiple($entity_type, array $entity_ids, $langcode = NULL) { // Double check entity IDs are numeric thanks to Entity API module. - $ids = array_filter(array_keys($ids, 'is_numeric')); + $entity_ids = array_filter($entity_ids, 'is_numeric'); - if ($metatags = metatag_metatags_load_multiple($type, $ids)) { + if ($metatags = metatag_metatags_load_multiple($entity_type, $entity_ids)) { $transaction = db_transaction(); try { - // Let other modules know about the metatags being deleted. - module_invoke_all('metatag_metatags_delete', $type, $ids); + // Let other modules know about the records being deleted using + // hook_metatag_metatags_delete(). + module_invoke_all('metatag_metatags_delete', $entity_type, $entity_ids, $langcode); + + // Set the entity to delete. + $query = db_delete('metatag') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_ids, 'IN'); + + // Specify a language if there is one. + if ($langcode) { + $query->condition('language', $langcode); + } - db_delete('metatag') - ->condition('entity_type', $type) - ->condition('entity_id', $ids, 'IN') - ->execute(); + // Perform the deletion(s). + $query->execute(); // Clear cached data. - metatag_metatags_cache_clear($type, $ids); + metatag_metatags_cache_clear($entity_type, $entity_ids); } catch (Exception $e) { $transaction->rollback(); @@ -364,14 +494,14 @@ function metatag_metatags_delete_multiple($type, array $ids) { } } -function metatag_metatags_cache_clear($type, $id = NULL) { - if (empty($id)) { - cache_clear_all("output:$type", 'cache_metatag', TRUE); +function metatag_metatags_cache_clear($entity_type, $entity_id = NULL) { + if (empty($entity_id)) { + cache_clear_all("output:$entity_type", 'cache_metatag', TRUE); } else { - $ids = (array) $id; - foreach ($ids as $id) { - cache_clear_all("output:$type:$id", 'cache_metatag', TRUE); + $entity_ids = (array) $entity_id; + foreach ($entity_ids as $entity_id) { + cache_clear_all("output:$entity_type:$entity_id", 'cache_metatag', TRUE); } } } @@ -379,10 +509,23 @@ function metatag_metatags_cache_clear($type, $id = NULL) { /** * Implements hook_entity_load(). */ -function metatag_entity_load($entities, $type) { - $metatags = metatag_metatags_load_multiple($type, array_keys($entities)); - foreach ($entities as $id => $entity) { - $entities[$id]->metatags = isset($metatags[$id]) ? $metatags[$id] : array(); +function metatag_entity_load($entities, $entity_type) { + // Wrap this in a try-catch block to work around occasions when the schema + // hasn't been updated yet. + try { + if (metatag_entity_supports_metatags($entity_type)) { + $metatags = metatag_metatags_load_multiple($entity_type, array_keys($entities)); + foreach ($entities as $entity_id => $entity) { + $entities[$entity_id]->metatags = isset($metatags[$entity_id]) ? $metatags[$entity_id] : array(); + } + } + } + catch (Exception $e) { + watchdog('metatag', 'Error loading meta tag data, do the <a href="@update">database updates</a> need to be run? The error occurred when loading record(s) %ids for the %type entity type. The error message was: %error', array('@update' => base_path() . 'update.php', '%ids' => implode(', ', array_keys($entities)), '%type' => $entity_type, '%error' => $e->getMessage()), WATCHDOG_CRITICAL); + // Don't display the same message twice for Drush. + if (php_sapi_name() != 'cli') { + drupal_set_message(t('Error loading meta tag data, do the <a href="@update">database updates</a> need to be run?', array('@update' => base_path() . 'update.php')), 'error'); + } } } @@ -391,8 +534,12 @@ function metatag_entity_load($entities, $type) { */ function metatag_entity_insert($entity, $entity_type) { if (isset($entity->metatags)) { - list($id) = entity_extract_ids($entity_type, $entity); - metatag_metatags_save($entity_type, $id, $entity->metatags); + list($entity_id) = entity_extract_ids($entity_type, $entity); + + // Determine the entity's language. + $language = metatag_entity_get_language($entity_type, $entity); + + metatag_metatags_save($entity_type, $entity_id, $entity->metatags, $language); } } @@ -400,14 +547,38 @@ function metatag_entity_insert($entity, $entity_type) { * Implements hook_entity_update(). */ function metatag_entity_update($entity, $entity_type) { - list($id) = entity_extract_ids($entity_type, $entity); + if (!metatag_entity_supports_metatags($entity_type)) { + return; + } + + list($entity_id) = entity_extract_ids($entity_type, $entity); if (isset($entity->metatags)) { - metatag_metatags_save($entity_type, $id, $entity->metatags); + // Determine the entity's language. + $new_language = metatag_entity_get_language($entity_type, $entity); + + // Determine the language for this entity object. + if (isset($entity->original)) { + $old_language = metatag_entity_get_language($entity_type, $entity->original); + + // If the language has changed then remove the old one. When a new + // translation is being saved using Entity Translation both values will + // be the same, so this is safe to do. + if ($old_language != $new_language) { + db_delete('metatag') + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->condition('language', $old_language) + ->execute(); + } + } + + // Save the record. + metatag_metatags_save($entity_type, $entity_id, $entity->metatags, $new_language); } else { // Still ensure the meta tag output is cached. - metatag_metatags_cache_clear($entity_type, $id); + metatag_metatags_cache_clear($entity_type, $entity_id); } } @@ -415,8 +586,8 @@ function metatag_entity_update($entity, $entity_type) { * Implements hook_entity_delete(). */ function metatag_entity_delete($entity, $entity_type) { - list($id) = entity_extract_ids($entity_type, $entity); - metatag_metatags_delete($entity_type, $id); + list($entity_id) = entity_extract_ids($entity_type, $entity); + metatag_metatags_delete($entity_type, $entity_id); } /** @@ -427,52 +598,117 @@ function metatag_field_attach_delete_revision($entity_type, $entity) { } /** - * Implements hook_field_attach_view_alter(). + * Implements hook_taxonomy_term_view_alter(). */ -function metatag_field_attach_view_alter(&$output, $context) { - $entity_type = $context['entity_type']; - $entity = $context['entity']; - list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); +function metatag_taxonomy_term_view_alter(&$build, &$entity_type) { + // This is only needed if hook_entity_view has not been added to core. + // @see http://drupal.org/node/1067120 + if (isset($build['#term']) && !function_exists('taxonomy_term_view_multiple')) { + $entity = taxonomy_term_load($build['#term']->tid); + metatag_entity_view($entity, $entity_type, 'full', NULL); + } +} + +/** + * Implements hook_entity_view(). + */ +function metatag_entity_view($entity, $entity_type, $view_mode, $langcode) { + // Only run this function once per page load. + static $i_will_say_this_only_once = FALSE; + + // Only proceed if this entity object is the page being viewed. + if (_metatag_entity_is_page($entity_type, $entity)) { + // Only run this function once per page load. + if ($i_will_say_this_only_once) { + return; + } + $i_will_say_this_only_once = TRUE; + + // If this entity object isn't allowed meta tags, skip it. + if (!metatag_entity_has_metatags($entity_type, $entity)) { + return; + } + + // Obbtain some details of the entity that are needed elsewhere. + list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); + $instance = "{$entity_type}:{$bundle}"; - if (metatag_entity_supports_metatags($entity_type, $bundle) && $context['view_mode'] == 'full' && _metatag_entity_is_page($entity_type, $entity)) { + // Determine the language this entity actually uses. + $entity_language = metatag_entity_get_language($entity_type, $entity); + + // The requested language is different to the entity's language, look for + // a language elsewhere. + if ($entity_language != $langcode) { + // If no language was defined for the entity then use that for the + if ($entity_language == LANGUAGE_NONE) { + $langcode = LANGUAGE_NONE; + } + else { + $enabled_languages = field_content_languages(); + foreach (field_language($entity_type, $entity) as $field => $lang) { + // Only accept actual language values that are properly enabled. + if ($lang != LANGUAGE_NONE && in_array($lang, $enabled_languages)) { + $langcode = $lang; + } + } + } + } + + // All applicable pieces for this current page. $cid_parts = array( - //'view_mode' => $context['view_mode'], - 'langcode' => $context['language'], - 'url' => $GLOBALS['base_url'] . '/' . current_path(), + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'entity_id' => $entity_id, + 'view_mode' => $view_mode, + 'langcode' => $langcode, + 'url' => $GLOBALS['base_url'] . base_path() . current_path(), ); - $cid = "output:{$entity_type}:{$entity_id}:" . hash('sha256', serialize($cid_parts)); + + // Allow each page in a sequence to have different values. + if (isset($_GET['page'])) { + $cid_parts['page'] = $_GET['page']; + } + + // Allow other modules to alter the page parts using + // hook_metatag_page_cache_cid_parts_alter(). + drupal_alter('metatag_page_cache_cid_parts', $cid_parts); + + $cid = "output:{$entity_type}:{$entity_id}:{$langcode}:" . hash('sha256', serialize($cid_parts)); if ($cache = cache_get($cid, 'cache_metatag')) { - $output['metatags'] = $cache->data; + $output = $cache->data; } else { + // Separate the meta tags. $metatags = isset($entity->metatags) ? $entity->metatags : array(); - $instance = "{$entity_type}:{$bundle}"; - // Build options for meta tag rendering. The context variable already - // contains entity type, entity, view mode, language, etc. - $options = $context; + // Build options for meta tag rendering. + $options = array( + 'entity' => $entity, + 'entity_type' => $entity_type, + 'view_mode' => $view_mode, + ); // Ensure we actually pass a language object rather than language code. $languages = language_list(); - if (isset($context['language']) && isset($languages[$context['language']])) { - $options['language'] = $languages[$context['language']]; + if (isset($languages[$langcode])) { + $options['language'] = $languages[$langcode]; } - // Reload the entity object from cache as it may have been altered by Panels. + // Reload the entity object from cache as it may have been altered. $token_type = token_get_entity_mapping('entity', $entity_type); $entities = entity_load($entity_type, array($entity_id)); $options['token data'][$token_type] = $entities[$entity_id]; $options['entity'] = $entities[$entity_id]; // Render the metatags and save to the cache. - $output['metatags'] = metatag_metatags_view($instance, $metatags, $options); - cache_set($cid, $output['metatags'], 'cache_metatag'); + $output = metatag_metatags_view($instance, $metatags, $options); + cache_set($cid, $output, 'cache_metatag'); } - // We have to add a '#field_type' property otherwise - // rdf_field_attach_view_alter() freaks out. - $output['metatags']['#field_type'] = NULL; + // We need to register the term's metatags, so we can later fetch them. + // @see metatag_page_build(). + metatag_page_set_metatags($instance, $output); } } @@ -480,9 +716,10 @@ function metatag_field_attach_view_alter(&$output, $context) { * Build a renderable array of meta tag output. * * @param string $instance - * The configuration instance key of the metatags to use, e.g. "node:article". + * The configuration instance key of the meta tags to use, e.g. + * "node:article". * @param array $metatags - * An arary of metatag data. + * An array of meta tag data. * @param array $options * (optional) An array of options including the following keys and values: * - language: A language object. @@ -491,7 +728,6 @@ function metatag_field_attach_view_alter(&$output, $context) { */ function metatag_metatags_view($instance, array $metatags = array(), array $options = array()) { $output = array(); - $metatags += metatag_config_load_with_defaults($instance); // Convert language codes to a language object. if (isset($options['language']) && is_string($options['language'])) { @@ -499,30 +735,58 @@ function metatag_metatags_view($instance, array $metatags = array(), array $opti $options['language'] = isset($languages[$options['language']]) ? $languages[$options['language']] : NULL; } + // If there are any tags, determine the translation to display. + if (!empty($metatags)) { + // Get the display language; default to the entity's language. + if (isset($options['language']) && isset($options['language']->language) && isset($metatags[$options['language']->language])) { + $metatags = $metatags[$options['language']->language]; + } + // If no language requested, use the no-language value. + elseif (!empty($metatags[LANGUAGE_NONE])) { + $metatags = $metatags[LANGUAGE_NONE]; + } + else { + $metatags = array(); + } + } + + // Add any default tags to the mix. + $metatags += metatag_config_load_with_defaults($instance); + foreach ($metatags as $metatag => $data) { if ($metatag_instance = metatag_get_instance($metatag, $data)) { $output[$metatag] = $metatag_instance->getElement($options); } } - drupal_alter('metatag_metatags_view', $output); + // Allow the output meta tags to be modified using + // hook_metatag_metatags_view_alter(). + drupal_alter('metatag_metatags_view', $output, $instance); return $output; } function metatag_metatags_values($instance, array $metatags = array(), array $options = array()) { $values = array(); - $metatags += metatag_config_load_with_defaults($instance); - // Convert language codes to a language object. - if (isset($options['language']) && is_string($options['language'])) { - $languages = language_list(); - $options['language'] = isset($languages[$options['language']]) ? $languages[$options['language']] : NULL; + // Apply defaults to the data for each language. + foreach ($metatags as $language => $metatag) { + $metatags[$language] += metatag_config_load_with_defaults($instance); } - foreach ($metatags as $metatag => $data) { - if ($metatag_instance = metatag_get_instance($metatag, $data)) { - $values[$metatag] = $metatag_instance->getValue($options); + // Generate output only if we have a valid language. + $language = $options['language']; + if (isset($language) && is_string($language) && isset($metatags[$language])) { + + // Convert language codes to a language object. + $languages = language_list(); + $options['language'] = isset($languages[$language]) ? $languages[$language] : NULL; + + // Get output elements. + foreach ($metatags[$language] as $metatag => $data) { + if ($metatag_instance = metatag_get_instance($metatag, $data)) { + $values[$metatag] = $metatag_instance->getValue($options); + } } } @@ -537,7 +801,7 @@ function metatag_metatags_values($instance, array $metatags = array(), array $op * @param string $instance * The configuration instance key of the metatags to use, e.g. "node:article". * @param array $metatags - * An arary of metatag data. + * An array of metatag data. * @param array $options * (optional) An array of options including the following keys and values: * - token types: An array of token types to be passed to theme_token_tree(). @@ -557,11 +821,12 @@ function metatag_metatags_form(array &$form, $instance, array $metatags = array( $form['metatags'] = array( '#type' => 'fieldset', '#title' => t('Meta tags'), + '#multilingual' => TRUE, '#collapsible' => TRUE, '#collapsed' => TRUE, '#tree' => TRUE, '#access' => user_access('edit meta tags') || user_access('administer meta tags'), - '#weight' => 10, + '#weight' => 40, '#attributes' => array( 'class' => array('metatags-form'), ), @@ -628,7 +893,8 @@ function metatag_metatags_form(array &$form, $instance, array $metatags = array( $form['metatags']['tokens'] = array( '#theme' => 'token_tree', '#token_types' => $options['token types'], - '#weight' => 100, + '#weight' => 999, + '#dialog' => TRUE, ); // Add a submit handler to compare the submitted values against the deafult @@ -657,7 +923,7 @@ function metatag_field_extra_fields() { $extra[$entity_type][$bundle]['form']['metatags'] = array( 'label' => t('Meta tags'), 'description' => t('Meta tag module form elements.'), - 'weight' => 10, + 'weight' => 40, ); } } @@ -665,55 +931,96 @@ function metatag_field_extra_fields() { return $extra; } +/** + * Check if an individual entity has meta tags defined, or has defaults. + * + * @param string $entity_type + * An entity type. + * @param object $entity + * An entity object. + * + * @return boolean + * TRUE or FALSE if the entity should have a form for or process meta tags. + */ +function metatag_entity_has_metatags($entity_type, $entity) { + // If an entity has custom meta tags assigned, then we should return TRUE. + if (!empty($entity->metatags)) { + return TRUE; + } + + // Otherwise, check to see if there exists any enabed configuration for + // either the entity type, or bundle (even if the configuration is empty). + // If no configuration exists, then we should not be displaying the meta tag + // forms or processing meta tags on entity view. + $config_exists = &drupal_static(__FUNCTION__, array()); + list( , , $bundle) = entity_extract_ids($entity_type, $entity); + // Do not pretend to have metatags when the bundle does not support them. + if (!metatag_entity_supports_metatags($entity_type, $bundle)) { + return FALSE; + } + $instance = "{$entity_type}:{$bundle}"; + if (!isset($config_exists[$instance])) { + // Check if the intstance or its parents (excluding global) are enabled. + $config_exists[$instance] = metatag_config_is_enabled($instance, TRUE, FALSE); + } + + return isset($config_exists[$instance]); +} + +/** + * Check whether the requested entity type (and bundle) support metatag. + * + * By default this will be FALSE, support has to be specifically enabled by + * assigning 'metatag' => TRUE within the hook_entity_info() definition for the + * entity. + */ function metatag_entity_supports_metatags($entity_type = NULL, $bundle = NULL) { - $types = &drupal_static(__FUNCTION__); + $entity_types = &drupal_static(__FUNCTION__); - if (!isset($types)) { - $types = array(); + if (!isset($entity_types)) { + $entity_types = array(); foreach (entity_get_info() as $entity_type_key => $entity_info) { - if (!isset($entity_info['metatags'])) { - // By default allow entities that have fields and have paths. - $entity_info['metatags'] = !empty($entity_info['uri callback']) && !empty($entity_info['fieldable']); - } if (empty($entity_info['metatags'])) { - $types[$entity_type_key] = FALSE; + $entity_types[$entity_type_key] = FALSE; continue; } - $types[$entity_type_key] = array(); + $entity_types[$entity_type_key] = array(); foreach ($entity_info['bundles'] as $bundle_key => $bundle_info) { - $types[$entity_type_key][$bundle_key] = !isset($bundle_info['metatags']) || !empty($bundle_info['metatags']); + $entity_types[$entity_type_key][$bundle_key] = !isset($bundle_info['metatags']) || !empty($bundle_info['metatags']); } } } if (isset($entity_type) && isset($bundle)) { - return isset($types[$entity_type][$bundle]) ? $types[$entity_type][$bundle] : FALSE; + return isset($entity_types[$entity_type][$bundle]) ? $entity_types[$entity_type][$bundle] : FALSE; } elseif (isset($entity_type)) { - return isset($types[$entity_type]) ? ($types[$entity_type] !== FALSE) : FALSE; + return isset($entity_types[$entity_type]) ? ($entity_types[$entity_type] !== FALSE) : FALSE; } - return $types; + return $entity_types; } /** * Implements hook_entity_info_alter(). + * + * Enables Metatag support for the core entities. */ function metatag_entity_info_alter(&$info) { $defaults['node'] = array( 'path' => 'node/%node', + 'metatags' => TRUE, ); $defaults['taxonomy_term'] = array( 'path' => 'taxonomy/term/%taxonomy_term', + 'metatags' => TRUE, ); if (module_exists('forum') && ($vid = variable_get('forum_nav_vocabulary', 0)) && $vocabulary = taxonomy_vocabulary_load($vid)) { $defaults['taxonomy_term']['bundles'][$vocabulary->machine_name]['path'] = 'forum/%taxonomy_term'; } $defaults['user'] = array( 'path' => 'user/%user', - ); - $defaults['comment'] = array( - 'metatags' => FALSE, + 'metatags' => TRUE, ); foreach ($defaults as $key => $entity_defaults) { @@ -756,36 +1063,122 @@ function metatag_load_entity_from_path($path) { } } + // Allow other modules to customize the data using + // hook_metatag_load_entity_from_path_alter(). drupal_alter('metatag_load_entity_from_path', $path, $result); + return $result; } +/** + * Add meta tags to be added later with metatag_page_build(). + * + * @param string $instance + * The configuration instance key of the meta tags, e.g. "node:article". + * @param array $metatags + * An array of meta tags from metatag_metatags_view(). + */ +function metatag_page_set_metatags($instance, $metatags) { + $page_metatags = &drupal_static(__FUNCTION__, array()); + $page_metatags[$instance] = $metatags; +} + +/** + * Retrieve the array of met tags to be added with metatag_page_build(). + */ +function metatag_page_get_metatags() { + // @todo Add alter to this result? + return drupal_static('metatag_page_set_metatags', array()); +} + /** * Implements hook_page_build(). */ function metatag_page_build(&$page) { - // For some reason with Overlay enabled we get an empty $page, so just fail - // this case. - if (!isset($page['content'])) { - return; + // Ensure these arrays exist, otherwise several use cases will fail. + if (!isset($page['content']) || !is_array($page['content'])) { + $page['content'] = array(); } + if (!isset($page['content']['metatags']) || !is_array($page['content']['metatags'])) { + $page['content']['metatags'] = array(); + } + + // The front page has special consideration. + $instance = 'global:frontpage'; + if (drupal_is_front_page() && metatag_config_is_enabled($instance)) { + $instance = 'global:frontpage'; - // Load the metatags render array in before any page content so that more - // more specific meta tags in the page content can override these meta tags. - $page['content'] = array('metatags' => array()) + $page['content']; - if (drupal_is_front_page()) { - $page['content']['metatags']['global:frontpage'] = metatag_metatags_view('global:frontpage', array()); + // These two parts are sufficient given that the homepage is unique. + $cid_parts = array( + 'langcode' => $GLOBALS['language_content']->language, + 'url' => $GLOBALS['base_url'] . base_path() . '<front>', + ); + + // Allow each page in a sequence to have different values. + if (isset($_GET['page'])) { + $cid_parts['page'] = $_GET['page']; + } + + // Allow other modules to customize the data using + // hook_metatag_page_cache_cid_parts_alter(). + drupal_alter('metatag_page_cache_cid_parts', $cid_parts); + + $cid = "output:{$instance}:" . hash('sha256', serialize($cid_parts)); + + if ($cache = cache_get($cid, 'cache_metatag')) { + $metatags = $cache->data; + } + else { + $metatags = metatag_metatags_view($instance, array()); + cache_set($cid, $metatags, 'cache_metatag'); + } + + $page['content']['metatags'][$instance] = $metatags; + } + + // Load any meta tags assigned via metatag_page_set_metatags(). Note: this + // must include the necessary defaults. + else { + $page['content']['metatags'] += metatag_page_get_metatags(); } - elseif (!path_is_admin(current_path())) { - // Do not output the global metatags when on an administration path. - $page['content']['metatags']['global'] = metatag_metatags_view('global', array()); + + // If no meta tags were loaded, and this is not an admin path, at least load + // the global defaults. This may be disabled, see README.txt for details. + if (empty($page['content']['metatags']) && variable_get('metatag_load_all_pages', TRUE) && !path_is_admin(current_path())) { + $instance = 'global'; + + // These two parts are sufficient given that the homepage is unique. + $cid_parts = array( + 'langcode' => $GLOBALS['language_content']->language, + 'url' => $GLOBALS['base_url'] . $_SERVER['REQUEST_URI'], + ); + + // Allow each page in a sequence to have different values. + if (isset($_GET['page'])) { + $cid_parts['page'] = $_GET['page']; + } + + // Allow other modules to customize the data using + // hook_metatag_page_cache_cid_parts_alter(). + drupal_alter('metatag_page_cache_cid_parts', $cid_parts); + + $cid = "output:{$instance}:" . hash('sha256', serialize($cid_parts)); + + if ($cache = cache_get($cid, 'cache_metatag')) { + $metatags = $cache->data; + } + else { + $metatags = metatag_metatags_view($instance, array()); + cache_set($cid, $metatags, 'cache_metatag'); + } + $page['content']['metatags'][$instance] = $metatags; } } /** * Returns whether the current page is the page of the passed in entity. * - * @param $type + * @param $entity_type * The entity type; e.g. 'node' or 'user'. * @param $entity * The entity object. @@ -794,9 +1187,9 @@ function metatag_page_build(&$page) { * TRUE if the current page is the page of the specified entity, or FALSE * otherwise. */ -function _metatag_entity_is_page($type, $entity) { - $uri = entity_uri($type, $entity); - return (!empty($uri) && current_path() == $uri['path']); +function _metatag_entity_is_page($entity_type, $entity) { + $uri = entity_uri($entity_type, $entity); + return !empty($uri['path']) && current_path() == $uri['path']; } /** @@ -824,15 +1217,49 @@ function metatag_field_attach_delete_bundle($entity_type, $bundle) { * Implements hook_field_attach_form(). */ function metatag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { - list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); - - if (!metatag_entity_supports_metatags($entity_type, $bundle)) { + if (!metatag_entity_has_metatags($entity_type, $entity)) { + return; + } + // Entity_Translation will trigger this hook again, skip it. + if (!empty($form_state['entity_translation']['is_translation'])) { return; } + list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity); $instance = "{$entity_type}:{$bundle}"; - $metatags = isset($entity->metatags) ? $entity->metatags : array(); + // Grab the meta tags for display in the form if there are any. + if (!empty($entity->metatags)) { + // Identify the language to use with this entity. + $entity_language = metatag_entity_get_language($entity_type, $entity); + + // If this is a new translation using Entity Translation, load the meta + // tags from the entity's original language. + if (module_exists('entity_translation') && empty($form['#entity_translation_source_form']) && ($handler = entity_translation_entity_form_get_handler($form, $form_state)) && isset($entity->metatags[$handler->getSourceLanguage()])) { + $metatags = $entity->metatags[$handler->getSourceLanguage()]; + } + // Determine from where we should get the tags. + elseif (isset($entity->metatags[$langcode])) { + // Set the tags to the translation set matching that of the form. + $metatags = $entity->metatags[$langcode]; + } + // There is no translation for this entity's tags in the current + // language. Instead, display tags in the language of the entity, the + // source language of translations. The will provide translators with the + // original text to translate. + elseif (isset($entity->metatags[$entity_language])) { + $metatags = $entity->metatags[$entity_language]; + } + // This is a preview so set the tags to the raw submission data. No + // language has been set. + else { + $metatags = $entity->metatags; + } + } + else { + $metatags = array(); + } + $options['token types'] = array(token_get_entity_mapping('entity', $entity_type)); $options['context'] = $entity_type; @@ -877,12 +1304,14 @@ function metatag_get_info($type = NULL, $name = NULL) { if (!isset($info)) { // hook_metatag_info() includes translated strings, so each language is cached // separately. - $cid = 'info:' . $GLOBALS['language']->language; + $cid = 'info:' . LANGUAGE_NONE; if ($cache = cache_get($cid, 'cache_metatag')) { $info = $cache->data; } else { + // Obtain all metatag specs defined in other modules using + // hook_metatag_info(). $info = module_invoke_all('metatag_info'); $info += array('tags' => array(), 'groups' => array()); @@ -895,8 +1324,10 @@ function metatag_get_info($type = NULL, $name = NULL) { ); } - // Let other modules alter the entity info and then cache it. + // Let other modules alter the entity info using + // hook_metatag_info_alter(). drupal_alter('metatag_info', $info); + cache_set($cid, $info, 'cache_metatag'); } } @@ -918,9 +1349,6 @@ function metatag_get_instance($metatag, array $data = array()) { $class = $info['class']; return new $class($info, $data); } - else { - trigger_error("Failed to load class {$info['class']} for metatag $metatag.", E_USER_ERROR); - } } /** @@ -1002,7 +1430,6 @@ function metatag_html_head_alter(&$elements) { foreach (array_keys($elements) as $key) { if (strpos($key, 'drupal_add_html_head_link:' . $name . ':') === 0) { unset($elements[$key]); - break; } } } @@ -1030,8 +1457,14 @@ function metatag_config_instance_info($instance = NULL) { $info = $cache->data; } else { + // Allow modules to act upon the record insertion using + // hook_metatag_config_instance_info(). $info = module_invoke_all('metatag_config_instance_info'); + + // Allow other modules to customize the data using + // hook_metatag_config_instance_info_alter(). drupal_alter('metatag_config_instance_info', $info); + cache_set($cid, $info, 'cache_metatag'); } } @@ -1101,12 +1534,20 @@ function metatag_config_instance_label($instance) { $labels = &drupal_static(__FUNCTION__, array()); if (!isset($labels[$instance])) { - $context = metatag_config_instance_info($instance); - $labels[$instance] = isset($context['label']) ? $context['label'] : t('Unknown'); - $parents = metatag_config_get_parent_instances($instance, FALSE); - array_shift($parents); - if (!empty($parents)) { - $labels[$instance] = metatag_config_instance_label(implode(':', $parents)) . ': ' . $labels[$instance]; + $instance_parts = explode(':', $instance); + $instance_part = array_pop($instance_parts); + if ($context = metatag_config_instance_info($instance)) { + $labels[$instance] = $context['label']; + } + else { + $labels[$instance] = t('Unknown (@instance)', array('@instance' => $instance_part)); + } + // Normally the following would use metatag_config_get_parent_instances() + // but since we already sliced the instance by separator and removed the + // last segment, putting the array back together gives us this instance's + // parent. + if (!empty($instance_parts)) { + $labels[$instance] = metatag_config_instance_label(implode(':', $instance_parts)) . ': ' . $labels[$instance]; } } @@ -1143,3 +1584,145 @@ function metatag_config_access($op, $config = NULL) { return FALSE; } + +/** + * Checks if a metatag configuration record is enabled. + * + * @param string $instance + * The configuration instance machine name. + * + * @return bool + * TRUE if the configuration is enabled, or FALSE otherwise. + */ +function metatag_config_is_enabled($instance, $include_defaults = FALSE, $include_global = TRUE) { + if ($include_defaults) { + return (bool) metatag_config_load_with_defaults($instance, $include_global); + } + else { + $config = metatag_config_load($instance); + return !empty($config) && empty($config->disabled); + } +} + +/** + * Wrapper around entity_language() to use LANGUAGE_NONE if the entity does not + * have a language assigned. + * + * @param $entity_type + * An entity type's machine name. + * @param $entity + * The entity to review; + * + * @return + * A string indicating the language code to be used. + */ +function metatag_entity_get_language($entity_type, $entity) { + // Determine the entity's language. + $langcode = entity_language($entity_type, $entity); + + // If no matching language was found, which will happen for e.g. terms and + // users, it is normally recommended to use the system default language. + // However, as the system default language can change, this could potentially + // cause data loss / confusion problems; as a result use the system "no + // language" value to avoid any potential problems. + if (empty($langcode)) { + $langcode = LANGUAGE_NONE; + } + + return $langcode; +} + +/** + * Implements of hook_features_api(). + */ +function metatag_features_api() { + $components = array( + 'metatag' => array( + 'name' => t('Meta tags'), + 'feature_source' => TRUE, + 'default_hook' => 'metatag_export_default', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'file' => drupal_get_path('module', 'metatag') . '/metatag.features.inc', + ), + ); + return $components; +} + +/** + * Implements hook_views_pre_render(). + */ +function metatag_views_post_render(&$view, &$output, &$cache) { + // Build a shortcut to the current display object. + $display = $view->display[$view->current_display]; + + // Only proceed if this view is a full page, don't process block or other + // Views display objects. + if ($display->display_plugin == 'page') { + // Check if this is an entity display page, if so trigger + // hook_entity_view(). + foreach (entity_get_info() as $entity_name => $entity_type) { + // Entity paths will include an auto-loader that matches the entity's + // name, thus the path will be 'some/path/%entity_name'. + if (isset($entity_type['path']) && ($display->display_options['path'] . $entity_name) == $entity_type['path']) { + // Only proceed if this entity type supports meta tags. + if (metatag_entity_supports_metatags($entity_name)) { + // There must be at least one argument and the first argument must be + // numerical. + if (!empty($view->args) && is_numeric($view->args[0])) { + // Only the first argument is used. + $entities = entity_load($entity_name, array($view->args[0])); + $entity = array_pop($entities); + metatag_entity_view($entity, $entity_name, 'full', NULL); + } + } + } + } + } +} + +/** + * Implements hook_ctools_render_alter(). + * + * Temporary solution to load meta tags on entity pages that are driven by + * CTools display handlers. + */ +function metatag_ctools_render_alter(&$info, $page, $context) { + // Only proceed if this is a full page (don't process individual panes) and + // there's an 'admin path' for the current task. + if ($page && !empty($context['task']['admin path'])) { + // Check if this is an entity display page, if so trigger + // hook_entity_view(). + foreach (entity_get_info() as $entity_name => $entity_type) { + // Entity paths will include an auto-loader that matches the entity's + // name, thus the path will be 'some/path/%entity_name'. + if (isset($entity_type['path']) && $context['task']['admin path'] == $entity_type['path']) { + // Only proceed if this entity type supports meta tags. + if (metatag_entity_supports_metatags($entity_name)) { + // There must be at least one argument and the first argument must be + // numerical. + if (!empty($context['args']) && is_numeric($context['args'][0])) { + // Only the first argument is used. + $entities = entity_load($entity_name, array($context['args'][0])); + $entity = array_pop($entities); + metatag_entity_view($entity, $entity_name, 'full', NULL); + } + } + } + } + } +} + +/** + * Implements hook_entity_translation_delete(). + * + * Required for content translations being handled via Entity_Translation to + * remove the appropriate record when a translation is removed without the + * corresponding entity record also being removed. + */ +function metatag_entity_translation_delete($entity_type, $entity, $langcode) { + // Get the entity's ID. + list($entity_id) = entity_extract_ids($entity_type, $entity); + + // Delete the translation. + metatag_metatags_delete($entity_type, $entity_id, $langcode); +} diff --git a/sites/all/modules/metatag/metatag.test b/sites/all/modules/metatag/metatag.test index 2133504d79d65fbc857b56b17dee228de49548ac..ad5076eca968521647f9c0edd2c6fc325e7394e1 100644 --- a/sites/all/modules/metatag/metatag.test +++ b/sites/all/modules/metatag/metatag.test @@ -26,25 +26,27 @@ class MetaTagsUnitTest extends MetaTagsTestHelper { $defaults = metatag_config_load_with_defaults('test:foo'); $this->assertEqual($defaults, array( 'description' => array('value' => 'Test foo description'), + 'abstract' => array('value' => 'Test foo abstract'), 'title' => array('value' => 'Test altered title'), 'test:foo' => array('value' => 'foobar'), 'generator' => array('value' => 'Drupal 7 (http://drupal.org)'), + 'canonical' => array('value' => '[current-page:url:absolute]'), + 'shortlink' => array('value' => '[current-page:url:unaliased]'), )); } public function testEntitySupport() { - $test_cases[0] = array('type' => 'node', 'expected' => TRUE); $test_cases[1] = array('type' => 'node', 'bundle' => 'article', 'expected' => TRUE); $test_cases[2] = array('type' => 'node', 'bundle' => 'page', 'expected' => TRUE); $test_cases[3] = array('type' => 'node', 'bundle' => 'invalid-bundle', 'expected' => FALSE); $test_cases[4] = array('type' => 'user', 'expected' => TRUE); - $test_cases[5] = array('type' => 'invalid-entity', 'expected' => FALSE); foreach ($test_cases as $test_case) { $test_case += array('bundle' => NULL); - $this->assertMetatagEntitySupportsMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); + $this->assertMetatagEntityHasMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); } variable_set('metatag_test_entity_info_disable', TRUE); + drupal_static_reset('metatag_entity_has_metatags'); drupal_static_reset('metatag_entity_supports_metatags'); entity_info_cache_clear(); @@ -52,19 +54,43 @@ class MetaTagsUnitTest extends MetaTagsTestHelper { $test_cases[4]['expected'] = FALSE; foreach ($test_cases as $test_case) { $test_case += array('bundle' => NULL); - $this->assertMetatagEntitySupportsMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); + $this->assertMetatagEntityHasMetatags($test_case['type'], $test_case['bundle'], $test_case['expected']); } } - function assertMetatagEntitySupportsMetatags($type, $bundle, $expected) { + function assertMetatagEntityHasMetatags($entity_type, $bundle, $expected) { + $entity = entity_create_stub_entity($entity_type, array(0, NULL, $bundle)); return $this->assertEqual( - metatag_entity_supports_metatags($type, $bundle), + metatag_entity_has_metatags($entity_type, $entity), $expected, - t("metatag_entity_supports_metatags(:type, :bundle) was :expected", array( - ':type' => var_export($type, TRUE), - ':bundle' => var_export($bundle, TRUE), + t("metatag_entity_has_metatags(:type, :entity) is :expected", array( + ':type' => var_export($entity_type, TRUE), + ':entity' => var_export($entity, TRUE), ':expected' => var_export($expected, TRUE), )) ); } + + /** + * Test the metatag_config_instance_label() function. + */ + public function testConfigLabels() { + $test_cases = array( + 'node' => 'Node', + 'node:article' => 'Node: Article', + 'node:article:c' => 'Node: Article: Unknown (c)', + 'node:b' => 'Node: Unknown (b)', + 'node:b:c' => 'Node: Unknown (b): Unknown (c)', + 'a' => 'Unknown (a)', + 'a:b' => 'Unknown (a): Unknown (b)', + 'a:b:c' => 'Unknown (a): Unknown (b): Unknown (c)', + 'a:b:c:d' => 'Unknown (a): Unknown (b): Unknown (c): Unknown (d)', + ); + + foreach ($test_cases as $input => $expected_output) { + drupal_static_reset('metatag_config_instance_label'); + $actual_output = metatag_config_instance_label($input); + $this->assertEqual($actual_output, $expected_output); + } + } } diff --git a/sites/all/modules/metatag/metatag_context/README.txt b/sites/all/modules/metatag/metatag_context/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..3499605888a233f5906c94f976be17b3c33c5f13 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/README.txt @@ -0,0 +1,21 @@ +Metatag Context +--------------- +This module is provides a Metatag reaction for Context [1], thus allowing meta +tags to be assigned to specific paths and other conditions. + +Configuration can controlled via the normal Context UI module or the new admin +page available at: admin/config/search/metatags/context + + +Credits +------------------------------------------------------------------------------ +This module is based on the Context Metadata [2] module. The initial +development was by Marcin Pajdzik [3] (sponsored by Dennis Publishing [4]). + + +References +------------------------------------------------------------------------------ +1: http://drupal.org/project/context +2: http://drupal.org/project/context_metadata +3: http://drupal.org/user/160555 +4: http://www.dennis.co.uk/ diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.admin.inc b/sites/all/modules/metatag/metatag_context/metatag_context.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..d776cb20682ac5db000ac75ade3863445592597c --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.admin.inc @@ -0,0 +1,219 @@ +<?php +/** + * @file + * Admin settings page for Metatag Context. + */ + +/** + * Provides administration overview page for metatags by path settings. + */ +function metatag_context_context_overview() { + $contexts = context_enabled_contexts(TRUE); + $header = array(t('Name'), t('Paths'), t('Operations')); + $rows = array(); + + $caption = t('Values assigned here inherit from the <a href="@url" title="Edit the global default meta tags.">global defaults</a> and will override any other meta tags assigned elsewhere.', array('@url' => url('admin/config/search/metatags/config/global'))); + + foreach ($contexts as $name => $context) { + // Only show context items that are specifically selected to be "Shown on + // metatag admin page". + if (isset($context->reactions['metatag_context_reaction']['metatag_admin']) && $context->reactions['metatag_context_reaction']['metatag_admin']) { + $ops = array( + l('Edit', 'admin/config/search/metatags/context/' . $context->name, array('query' => array('destination' => 'admin/config/search/metatags/context'))), + l('Delete', 'admin/config/search/metatags/context/' . $context->name . '/delete', array('query' => array('destination' => 'admin/config/search/metatags/context'))), + ); + $rows[] = array( + $context->name, + isset($context->conditions['path']) ? implode(', ', $context->conditions['path']['values']) : t('No path condition.'), + implode(' | ', $ops), + ); + } + } + return theme('table', array('header' => $header, 'rows' => $rows, 'caption' => $caption)); +} + +function metatag_context_config_add_form($form, &$form_state) { + $form['name'] = array( + '#title' => 'Name', + '#type' => 'textfield', + '#default_value' => '', + '#description' => 'The unique ID for this metatag path context rule. This must contain only lower case letters, numbers and underscores.', + '#required' => 1, + '#maxlength' => 255, + '#element_validate' => array('metatag_context_edit_name_validate'), + ); + + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Add and configure'), + ); + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/metatags/context', + ); + return $form; +} + +function metatag_context_edit_name_validate($element, &$form_state) { + // Check for string identifier sanity. + if (!preg_match('!^[a-z0-9_-]+$!', $element['#value'])) { + form_error($element, t('The name can only consist of lowercase letters, underscores, dashes, and numbers.')); + return; + } + + // Ensure the CTools exportables system is loaded. + ctools_include('export'); + + // Check for name collision. + if ($exists = ctools_export_crud_load('context', $element['#value'])) { + form_error($element, t('A context with this name already exists. Please choose another name or delete the existing item before creating a new one.')); + } +} + +function metatag_context_config_add_form_submit($form, &$form_state) { + $context = metatag_context_load_default_context(); + $context->name = $form_state['values']['name']; + context_save($context); + $form_state['redirect'] = 'admin/config/search/metatags/context/' . $context->name; +} + +function metatag_context_config_edit_form($form, &$form_state, $context) { + $form_state['metatag_context']['context'] = $context; + + // Empty form to start with. + $form = array(); + // Don't care about the instance name, the data is being managed by Context + // and not Metatag. + $instance = ""; + $options = array(); + + // Load the METATAG form. + metatag_metatags_form($form, $instance, $context->reactions['metatag_context_reaction']['metatags'], $options); + + $form['paths'] = array( + '#title' => 'Path', + '#description' => t('Set this metatag context when any of the paths above match the page path. Put each path on a separate line. You can use the <code>*</code> character (asterisk) as a wildcard and the <code>~</code> character (tilde) to exclude one or more paths. Use <code><front></code> for the site front page. Only local paths (e.g. "example/page") will work, do not use relative URLs ("/example/page") or absolute URLs ("http://example.com/example/page").'), + '#type' => 'textarea', + '#default_value' => isset($context->conditions['path']['values']) ? html_entity_decode(implode(' ', $context->conditions['path']['values'])) : '', + '#required' => 1, + '#weight' => -100, + ); + + // If other conditions are assigned, mention it. + $conditions = array_keys($context->conditions); + foreach ($conditions as $key => $condition) { + if ($condition == 'path') { + unset($conditions[$key]); + } + } + if (!empty($conditions)) { + $form['other_conditions'] = array( + '#prefix' => '<p><em>', + '#markup' => t('Other conditions have been assigned that must be controlled through the main Context settings page.'), + '#suffix' => '</em></p>', + '#weight' => -99.9, + ); + } + + $form['help'] = array( + '#prefix' => '<hr /><p><em>', + '#markup' => t('Values assigned here inherit from the <a href="@url" title="Edit the global default meta tags.">global defaults</a> and will override any other meta tags assigned elsewhere.', array('@url' => url('admin/config/search/metatags/config/global'))), + '#suffix' => '</em></p>', + '#weight' => -99, + ); + + // Show all tokens. + $form['metatags']['tokens']['#token_types'] = 'all'; + + $form['metatags']['#type'] = 'container'; + unset($form['metatags']['#collapsed']); + unset($form['metatags']['#collapsible']); + + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + $form['actions']['cancel'] = array( + '#type' => 'submit', + '#value' => t('Cancel'), + '#submit' => array('metatag_context_config_edit_form_cancel_submit'), + '#limit_validation_errors' => array(), + ); + $form['#submit'][] = 'metatag_context_config_edit_form_submit'; + + return $form; +} + +function metatag_context_config_edit_form_cancel_submit($form, &$form_state) { + context_delete($form_state['metatag_context']['context']); + $form_state['redirect'] = 'admin/config/search/metatags/context'; +} + +function metatag_context_config_edit_form_submit($form, &$form_state) { + $context = $form_state['metatag_context']['context']; + $context->reactions['metatag_context_reaction']['metatags'] = array_merge($context->reactions['metatag_context_reaction']['metatags'], $form_state['values']['metatags']); + $paths = explode("\n", str_replace("\r", "", $form_state['values']['paths'])); + $paths = array_combine($paths, $paths); + $context->conditions['path']['values'] = $paths; + context_save($context); + $form_state['redirect'] = 'admin/config/search/metatags/context'; +} + +function metatag_context_delete_form($form, &$form_state, $context) { + $form_state['metatag_context']['context'] = $context; + + $form['delete'] = array( + '#value' => 'This action will permanently remove this item from your database.' + ); + + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + ); + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/search/metatags/context', + ); + $form['#submit'][] = 'metatag_context_delete_form_submit'; + + return $form; +} + +function metatag_context_delete_form_submit($form, &$form_state) { + context_delete($form_state['metatag_context']['context']); + $form_state['redirect'] = 'admin/config/search/metatags/context'; +} + +function metatag_context_load_default_context() { + $context = new stdClass(); + $context->disabled = FALSE; /* Edit this to true to make a default context disabled initially */ + $context->api_version = 3; + $context->name = 'default_metatag_context'; + $context->description = ''; + $context->tag = 'Metatag'; + $context->metatag = TRUE; + $context->conditions = array( + 'path' => array( + 'values' => array( + ), + ), + ); + $context->reactions = array( + 'metatag_context_reaction' => array( + 'metatags' => array(), + 'metatag_admin' => 1, + ), + ); + $context->condition_mode = 0; + $context->weight = 0; + + // Translatables + // Included for use with string extractors like potx. + t('Metatag'); + return $context; +} diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.context.inc b/sites/all/modules/metatag/metatag_context/metatag_context.context.inc new file mode 100644 index 0000000000000000000000000000000000000000..70f67232abc208d4677682b4a09ea16794796fd1 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.context.inc @@ -0,0 +1,134 @@ +<?php +/** + * @file + * Context reaction for Metatag. + */ + +class metatag_context_reaction extends context_reaction { + function options_form($context) { + $form = array(); + + // Don't care about the instance name, the data is being managed by + // Context and not Metatag. + $instance = ""; + // Load the previously saved settings. + $data = $this->fetch_from_context($context); + if (!isset($data['metatags'])) { + $data['metatags'] = array(); + } + // No options currently available. + $options = array(); + + $form['help'] = array( + '#prefix' => '<p><em>', + '#markup' => t('Values assigned here inherit from the <a href="@url" title="Edit the global default meta tags.">global defaults</a> and will override any other meta tags assigned elsewhere.', array('@url' => url('admin/config/search/metatags/config/global'))), + '#suffix' => '</em></p>', + ); + + $form['basic_header'] = array( + '#prefix' => '<hr /><h3>', + '#markup' => t('Basic tags'), + '#suffix' => '</h3>', + ); + + // Load the basic Metatag form. + metatag_metatags_form($form, $instance, $data['metatags'], $options); + + // Stop the meta tag fields appearing within a fieldset. + $form['metatags']['#type'] = 'container'; + unset($form['metatags']['#collapsed']); + unset($form['metatags']['#collapsible']); + unset($form['#submit']); + + // Flatten the fieldsets because otherwise the Context module will not save + // them properly. + // TODO: Perhaps it can be done in a better way with #tree and #parents? + foreach (array('advanced', 'dublin-core', 'open-graph') as $fieldset) { + if (isset($form['metatags'][$fieldset])) { + $form['metatags'][$fieldset . '_heading'] = array( + '#prefix' => '<hr /><h3>', + '#markup' => $form['metatags'][$fieldset]['#title'], + '#suffix' => '</h3>', + ); + if (isset($form['metatags'][$fieldset]['#description'])) { + $form['metatags'][$fieldset . '_description'] = array( + '#prefix' => '<p>', + '#markup' => $form['metatags'][$fieldset]['#description'], + '#suffix' => '</p>', + ); + } + foreach ($form['metatags'][$fieldset] as $key => $value) { + if (substr($key, 0, 1) == '#') { + unset ($form['metatags'][$fieldset][$key]); + continue; + } + $form['metatags'][$key] = $value; + unset($form['metatags'][$key]['#parents']); + unset($form['metatags'][$fieldset][$key]); + } + unset($form['metatags'][$fieldset]); + } + } + + // Show all takens. + $form['metatags']['tokens']['#token_types'] = 'all'; + + $form['metatag_admin'] = array( + '#type' => 'checkbox', + '#title' => t('Show on metatag admin page.'), + '#weight' => -98, + '#default_value' => isset($data['metatag_admin']) ? $data['metatag_admin'] : '', + ); + + return $form; + } + + /** + * Output a list of active contexts. + */ + function execute() { + $output = &drupal_static('metatag_context'); + + if (!isset($output)) { + $metatags = array(); + $output = array(); + $contexts = context_active_contexts(); + $options = array(); + $instance_names = array(); + + foreach ($contexts as $context) { + if (!empty($context->reactions['metatag_context_reaction']['metatags'])) { + $metadata_array = $context->reactions['metatag_context_reaction']['metatags']; + foreach ($metadata_array as $key => $data) { + if (!empty($data['value'])) { + $metatags[$key] = $data;//t(check_plain($data['value'])); + } + } + + // Add this context to the list of instances. + $instance_names[] = $context->name; + } + } + + // Only proceed if metatags were assigned. + if (!empty($metatags)) { + $metatags += metatag_config_load_with_defaults(''); + + foreach ($metatags as $metatag => $data) { + if ($metatag_instance = metatag_get_instance($metatag, $data)) { + $output[$metatag] = $metatag_instance->getElement($options); + } + } + + // Compile the identifier for this combination based on the context + // names. + asort($instance_names); + $instance = 'context:' . implode(',', $instance_names); + + // Allow the output meta tags to be modified using + // hook_metatag_metatags_view_alter(). + drupal_alter('metatag_metatags_view', $output, $instance); + } + } + } +} diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.info b/sites/all/modules/metatag/metatag_context/metatag_context.info new file mode 100644 index 0000000000000000000000000000000000000000..14d32b947fe9aa8c888beafeae26178297b007ea --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.info @@ -0,0 +1,13 @@ +name = Meta tags: Context +description = "Assigned Meta tags using Context definitions, allowing them to be assigned by path and other criteria." +package = Meta tags +core = 7.x +dependencies[] = context +dependencies[] = metatag + +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" +core = "7.x" +project = "metatag" +datestamp = "1364088611" + diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.install b/sites/all/modules/metatag/metatag_context/metatag_context.install new file mode 100644 index 0000000000000000000000000000000000000000..78415ce60305a4e2101699c3e87e909c00d55725 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.install @@ -0,0 +1,13 @@ +<?php +/** + * @file + * Installation and update hooks for Metatag:Context. + */ + +/** + * Implements hook_enable(). + */ +function metatag_context_enable() { + // Clear the cache so Context and CTools know about this plugin. + cache_clear_all('plugins:context:plugins', 'cache'); +} diff --git a/sites/all/modules/metatag/metatag_context/metatag_context.module b/sites/all/modules/metatag/metatag_context/metatag_context.module new file mode 100644 index 0000000000000000000000000000000000000000..8be7312f5a59fee29df530ba0f7f293c75a3d786 --- /dev/null +++ b/sites/all/modules/metatag/metatag_context/metatag_context.module @@ -0,0 +1,105 @@ +<?php +/** + * @file + * Primary hook implementations for Metatag Context. + */ + +/** + * Implements hook_menu(). + */ +function metatag_context_menu() { + $items['admin/config/search/metatags/context'] = array( + 'title' => 'By path', + 'page callback' => 'metatag_context_context_overview', + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/config/search/metatags/context/add'] = array( + 'title' => 'Add a meta tag by path', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_context_config_add_form'), + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + 'type' => MENU_LOCAL_ACTION, + ); + $items['admin/config/search/metatags/context/%context'] = array( + 'title' => 'Configure metatags by path', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_context_config_edit_form', 5), + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + ); + $items['admin/config/search/metatags/context/%context/delete'] = array( + 'title' => 'Delete metatags by path', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('metatag_context_delete_form', 5), + 'access arguments' => array('administer meta tags'), + 'file' => 'metatag_context.admin.inc', + ); + + return $items; +} + +/** + * Implements hook_context_plugins(). + */ +function metatag_context_context_plugins() { + return array( + 'metatag_context_reaction' => array( + 'handler' => array( + 'path' => drupal_get_path('module', 'metatag_context'), + 'file' => 'metatag_context.context.inc', + 'class' => 'metatag_context_reaction', + 'parent' => 'context_reaction', + ), + ), + ); +} + +/** + * Implements hook_context_registry(). + */ +function metatag_context_context_registry() { + return array( + 'reactions' => array( + 'metatag_context_reaction' => array( + 'title' => t('Meta Data'), + 'description' => t('Control page meta tags using the Metatag module.'), + 'plugin' => 'metatag_context_reaction', + ), + ), + ); +} + +/** + * Implements hook_context_page_reaction(). + */ +function metatag_context_context_page_reaction() { + if ($plugin = context_get_plugin('reaction', 'metatag_context_reaction')) { + $plugin->execute(); + } +} + +/** + * Implements hook_page_build(). + */ +function metatag_context_page_build(&$page) { + // Load the meta tags that have been generated for this page. + $metatags = drupal_static('metatag_context', array()); + + if (!empty($metatags)) { + $page['content']['metatags']['global'] = $metatags; + } +} + +/** + * Implements hook_preprocess_html(). + */ +function metatag_context_preprocess_html(&$variables) { + $metadata = drupal_static('metatag_context'); + + if (isset($metadata['metadata_title'])) { + $variables['head_title'] = token_replace($metadata['metadata_title']); + } +} diff --git a/sites/all/modules/metatag/metatag_dc/README.txt b/sites/all/modules/metatag/metatag_dc/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..d95921887f4db8384f41bd3268fe1cf9380b242a --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/README.txt @@ -0,0 +1,36 @@ +Metatag: Dublin Core +-------------------- +This module adds the fifteen Dublin Core Metadata Element Set [1] to the +available meta tags, as defined by the Dublin Core Metadata Institute [2]. + +The following tags are provided: +* dcterms.contributor +* dcterms.coverage +* dcterms.creator +* dcterms.date +* dcterms.description +* dcterms.format +* dcterms.identifier +* dcterms.language +* dcterms.publisher +* dcterms.relation +* dcterms.rights +* dcterms.source +* dcterms.subject +* dcterms.title +* dcterms.type + + +Credits +------------------------------------------------------------------------------ +The initial development was by Marty2081 [3] (sponsored by Gemeentemuseum Den +Haag. [4]), with contributions by many in the community [5]. + + +References +------------------------------------------------------------------------------ +1: http://dublincore.org/documents/dces/ +2: http://www.dublincore.org/ +3: http://drupal.org/user/960720 +4: http://www.gemeentemuseum.nl/ +5: http://drupal.org/node/1491616 diff --git a/sites/all/modules/metatag/metatag_dc/metatag_dc.info b/sites/all/modules/metatag/metatag_dc/metatag_dc.info new file mode 100644 index 0000000000000000000000000000000000000000..7e05d4efc9c33d76fe51bc62e2a62207d63ce468 --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/metatag_dc.info @@ -0,0 +1,12 @@ +name = Meta tags: Dublin Core +description = Provides the fifteen <a href="http://dublincore.org/documents/dces/">Dublin Core Metadata Element Set 1.1</a> meta tags from the <a href="http://dublincore.org/">Dublin Core Metadata Institute</a>. +package = Meta tags +core = 7.x +dependencies[] = metatag + +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" +core = "7.x" +project = "metatag" +datestamp = "1364088611" + diff --git a/sites/all/modules/metatag/metatag_dc/metatag_dc.metatag.inc b/sites/all/modules/metatag/metatag_dc/metatag_dc.metatag.inc new file mode 100644 index 0000000000000000000000000000000000000000..7f8094d157794ba486746d1d17267fa727a7c62d --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/metatag_dc.metatag.inc @@ -0,0 +1,235 @@ +<?php +/** + * @file + * Metatag integration for the metatag_dc module. + */ + +/** + * Implements hook_metatag_config_default_alter(). + */ +function metatag_dc_metatag_config_default_alter(array &$configs) { + foreach ($configs as &$config) { + switch ($config->instance) { + case 'global': + $config->config += array( + 'dcterms.title' => array('value' => '[current-page:title]'), + 'dcterms.type' => array('value' => 'Text'), + 'dcterms.format' => array('value' => 'text/html'), + ); + break; + + case 'global:frontpage': + $config->config += array( + 'dcterms.title' => array('value' => '[site:name]'), + 'dcterms.identifier' => array('value' => '[site:url]'), + ); + break; + + case 'node': + $config->config += array( + 'dcterms.title' => array('value' => '[node:title]'), + 'dcterms.date' => array('value' => '[node:created:custom:Y-m-d\TH:iP]'), + 'dcterms.identifier' => array('value' => '[current-page:url:absolute]'), + 'dcterms.language' => array('value' => '[node:language]'), + 'dcterms.creator' => array('value' => '[node:author]'), + ); + break; + + case 'taxonomy_term': + $config->config += array( + 'dcterms.title' => array('value' => '[term:name]'), + 'dcterms.identifier' => array('value' => '[current-page:url:absolute]'), + 'dcterms.description' => array('value' => '[term:description]'), + ); + break; + + case 'user': + $config->config += array( + 'dcterms.title' => array('value' => '[user:name]'), + 'dcterms.date' => array('value' => '[user:created:custom:Y-m-d\TH:iP]'), + 'dcterms.identifier' => array('value' => '[current-page:url:absolute]'), + 'dcterms.creator' => array('value' => '[user:name]'), + ); + break; + } + } +} + +/** + * Implements hook_metatag_info(). + * Dublin Core Elements taken from http://purl.org/dc/elements/1.1/. + */ +function metatag_dc_metatag_info() { + $info['groups']['dublin-core'] = array( + 'label' => t('Dublin Core'), + 'description' => t('The Dublin Core Metadata Element Set, aka "Dublin Core meta tags", are a set of internationally standardized metadata tags used to describe content to make identification and classification of content easier; the standards are controlled by the <a href="http://dublincore.org/">Dublin Core Metadata Initiative (DCMI)</a>.'), + 'form' => array( + '#weight' => 70, + ), + ); + $info['tags']['dcterms.title'] = array( + 'label' => t('Dublin Core Title'), + 'description' => t('The name given to the resource.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#type' => 'term', + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.creator'] = array( + 'label' => t('Dublin Core Creator'), + 'description' => t('An entity primarily responsible for making the resource. Examples of a Creator include a person, an organization, or a service. Typically, the name of a Creator should be used to indicate the entity.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.subject'] = array( + 'label' => t('Dublin Core Subject'), + 'description' => t('The topic of the resource. Typically, the subject will be represented using keywords, key phrases, or classification codes. Recommended best practice is to use a controlled vocabulary. To describe the spatial or temporal topic of the resource, use the Coverage element.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.description'] = array( + 'label' => t('Dublin Core Description'), + 'description' => t('An account of the resource. Description may include but is not limited to: an abstract, a table of contents, a graphical representation, or a free-text account of the resource.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.publisher'] = array( + 'label' => t('Dublin Core Publisher'), + 'description' => t('An entity responsible for making the resource available. Examples of a Publisher include a person, an organization, or a service. Typically, the name of a Publisher should be used to indicate the entity.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.contributor'] = array( + 'label' => t('Dublin Core Contributor'), + 'description' => t('An entity responsible for making contributions to the resource. Examples of a Contributor include a person, an organization, or a service. Typically, the name of a Contributor should be used to indicate the entity.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.date'] = array( + 'label' => t('Dublin Core Date'), + 'description' => t('A point or period of time associated with an event in the lifecycle of the resource. Date may be used to express temporal information at any level of granularity. Recommended best practice is to use an encoding scheme, such as the W3CDTF profile of ISO 8601 [W3CDTF].'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.type'] = array( + 'label' => t('Dublin Core Type'), + 'description' => t('The nature or genre of the resource. Recommended best practice is to use a controlled vocabulary such as the DCMI Type Vocabulary [DCMITYPE]. To describe the file format, physical medium, or dimensions of the resource, use the Format element.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'form' => array( + '#type' => 'select', + '#options' => _metatag_dc_dcmi_type_vocabulary_options(), + '#empty_option' => t('- None -'), + ), + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.format'] = array( + 'label' => t('Dublin Core Format'), + 'description' => t('The file format, physical medium, or dimensions of the resource. Examples of dimensions include size and duration. Recommended best practice is to use a controlled vocabulary such as the list of Internet Media Types [MIME].'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.identifier'] = array( + 'label' => t('Dublin Core Identifier'), + 'description' => t('An unambiguous reference to the resource within a given context. Recommended best practice is to identify the resource by means of a string conforming to a formal identification system.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.source'] = array( + 'label' => t('Dublin Core Source'), + 'description' => t('A related resource from which the described resource is derived. The described resource may be derived from the related resource in whole or in part. Recommended best practice is to identify the related resource by means of a string conforming to a formal identification system.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.language'] = array( + 'label' => t('Dublin Core Language'), + 'description' => t('A language of the resource. Recommended best practice is to use a controlled vocabulary such as RFC 4646 [RFC4646].'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'dublin-core', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.relation'] = array( + 'label' => t('Dublin Core Relation'), + 'description' => t('A related resource. Recommended best practice is to identify the related resource by means of a string conforming to a formal identification system.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.coverage'] = array( + 'label' => t('Dublin Core Coverage'), + 'description' => t('The spatial or temporal topic of the resource, the spatial applicability of the resource, or the jurisdiction under which the resource is relevant. Spatial topic and spatial applicability may be a named place or a location specified by its geographic coordinates. Temporal topic may be a named period, date, or date range. A jurisdiction may be a named administrative entity or a geographic place to which the resource applies. Recommended best practice is to use a controlled vocabulary such as the Thesaurus of Geographic Names [TGN]. Where appropriate, named places or time periods can be used in preference to numeric identifiers such as sets of coordinates or date ranges.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + $info['tags']['dcterms.rights'] = array( + 'label' => t('Dublin Core Rights'), + 'description' => t('Information about rights held in and over the resource. Typically, rights information includes a statement about various property rights associated with the resource, including intellectual property rights.'), + 'group' => 'dublin-core', + 'class' => 'DrupalTextMetaTag', + 'element' => array( + '#theme' => 'metatag_dc', + ), + ); + + return $info; +} + +/** + * Function that returns the DCMI type options. + * Types taken from http://dublincore.org/documents/dcmi-type-vocabulary/. + */ +function _metatag_dc_dcmi_type_vocabulary_options() { + $options = array( + 'Collection', + 'Dataset', + 'Event', + 'Image', + 'InteractiveResource', + 'MovingImage', + 'PhysicalObject', + 'Service', + 'Software', + 'Sound', + 'StillImage', + 'Text', + ); + return drupal_map_assoc($options); +} diff --git a/sites/all/modules/metatag/metatag_dc/metatag_dc.module b/sites/all/modules/metatag/metatag_dc/metatag_dc.module new file mode 100644 index 0000000000000000000000000000000000000000..6090aff71f7f662f750defa5f404d22b3fac1f24 --- /dev/null +++ b/sites/all/modules/metatag/metatag_dc/metatag_dc.module @@ -0,0 +1,39 @@ +<?php +/** + * @file + * Metatag integration for the metatag_dc module. + */ + +/** + * Implements hook_ctools_plugin_api(). + */ +function metatag_dc_ctools_plugin_api($owner, $api) { + if ($owner == 'metatag' && $api == 'metatag') { + return array('version' => 1); + } +} + +/** + * Implements hook_theme(). + */ +function metatag_dc_theme() { + $info['metatag_dc'] = array( + 'render element' => 'element', + ); + + return $info; +} + +/** + * Theme callback for a Dublin Core meta tag. + */ +function theme_metatag_dc($variables) { + $element = &$variables['element']; + element_set_attributes($element, array( + '#name' => 'property', + '#schema' => 'schema', + '#value' => 'content') + ); + unset($element['#value']); + return theme('html_tag', $variables); +} diff --git a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info index 32df12e2ad61a7a9cc3bfdcc5ac15eb3d96e264f..6b32cfdfccd7c2bcb555691816c7e994944b891f 100644 --- a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info +++ b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.info @@ -1,12 +1,12 @@ -name = Open Graph meta tags +name = Meta tags: Open Graph description = Provides support for open graph meta tags. package = Meta tags core = 7.x dependencies[] = metatag -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc index a6d9dc7b0454cff7bb6b611bd8b7d5fcfaaa7870..f6c6a1b2a5bf93f7a352ef685a173a7f76995e9e 100644 --- a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc +++ b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.metatag.inc @@ -16,7 +16,7 @@ function metatag_opengraph_metatag_config_default_alter(array &$configs) { 'og:type' => array('value' => 'article'), 'og:title' => array('value' => '[current-page:title]'), 'og:site_name' => array('value' => '[site:name]'), - 'og:url' => array('value' => '[current-page:url]'), + 'og:url' => array('value' => '[current-page:url:absolute]'), ); break; case 'global:frontpage': @@ -60,6 +60,39 @@ function metatag_opengraph_metatag_config_default_alter(array &$configs) { function metatag_opengraph_metatag_info() { $info['groups']['open-graph'] = array( 'label' => t('Open Graph'), + 'form' => array( + '#weight' => 50, + ), + ); + + $info['tags']['fb:admins'] = array( + 'label' => t('Facebook Admins'), + 'description' => t('A comma-separated list of Facebook user IDs of people who are considered administrators or moderators of this page. Most sites will only need to assign this on the global settings page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['fb:app_id'] = array( + 'label' => t('Facebook Application ID'), + 'description' => t('A comma-separated list of Facebook Platform Application IDs applicable for this site. Most sites will only need to assign this on the global settings page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:site_name'] = array( + 'label' => t('Open Graph site name'), + 'description' => t('A human-readable name for your site, e.g., <em>IMDb</em>.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'context' => array('global'), + 'element' => array( + '#theme' => 'metatag_opengraph', + ), ); $info['tags']['og:title'] = array( @@ -71,23 +104,41 @@ function metatag_opengraph_metatag_info() { '#theme' => 'metatag_opengraph', ), ); + + $info['tags']['og:description'] = array( + 'label' => t('Open Graph description'), + 'description' => t('A one to two sentence description of your page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:type'] = array( 'label' => t('Open Graph type'), 'description' => t('The type of your object, e.g., <em>movie</em>.'), 'class' => 'DrupalTextMetaTag', 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), 'form' => array( '#type' => 'select', '#options' => _metatag_opengraph_type_options(), '#empty_option' => t('- None -'), ), - 'element' => array( - '#theme' => 'metatag_opengraph', - ), ); - //if (module_exists('select_or_other')) { - // $info['tags']['og:type']['form']['#type'] = 'select_or_other'; - //} + + if (module_exists('select_or_other')) { + // Enhance the og:type field to support custom types. + $info['tags']['og:type']['form']['#type'] = 'select_or_other'; + $info['tags']['og:type']['form']['#other'] = t('Other (please type a value)'); + $info['tags']['og:type']['form']['#other_unknown_defaults'] = 'other'; + $info['tags']['og:type']['form']['#theme'] = 'select_or_other'; + $info['tags']['og:type']['form']['#element_validate'] = array('select_or_other_element_validate'); + } + $info['tags']['og:image'] = array( 'label' => t('Open Graph image'), 'description' => t('An image URL which should represent your object within the graph. The image must be at least 50px by 50px and have a maximum aspect ratio of 3:1. We support PNG, JPEG and GIF formats.'), @@ -97,6 +148,7 @@ function metatag_opengraph_metatag_info() { '#theme' => 'metatag_opengraph', ), ); + $info['tags']['og:url'] = array( 'label' => t('Open Graph URL'), 'description' => t('The canonical URL of your object that will be used as its permanent ID in the graph, e.g., <em>http://www.imdb.com/title/tt0117500/</em>.'), @@ -106,25 +158,153 @@ function metatag_opengraph_metatag_info() { '#theme' => 'metatag_opengraph', ), ); - $info['tags']['og:site_name'] = array( - 'label' => t('Open Graph site name'), - 'description' => t('A human-readable name for your site, e.g., <em>IMDb</em>.'), + + $info['tags']['og:latitude'] = array( + 'label' => t('Open Graph Latitude'), + 'description' => '', 'class' => 'DrupalTextMetaTag', 'group' => 'open-graph', - 'context' => array('global'), 'element' => array( '#theme' => 'metatag_opengraph', ), ); - $info['tags']['og:description'] = array( - 'label' => t('Open Graph description'), - 'description' => t('A one to two sentence description of your page.'), + $info['tags']['og:longitude'] = array( + 'label' => t('Open Graph Longitude'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:street-address'] = array( + 'label' => t('Open Graph Street Address'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:locality'] = array( + 'label' => t('Open Graph Locality'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:region'] = array( + 'label' => t('Open Graph Region'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:postal-code'] = array( + 'label' => t('Open Graph Postal Code'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:country-name'] = array( + 'label' => t('Open Graph Country Name'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:email'] = array( + 'label' => t('Open Graph Email'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:phone_number'] = array( + 'label' => t('Open Graph Phone Number'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:fax_number'] = array( + 'label' => t('Open Graph Fax Number'), + 'description' => '', + 'class' => 'DrupalTextMetaTag', 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + + $info['tags']['og:video'] = array( + 'label' => t('Open Graph Video (URL)'), + 'description' => t('A URL to a video file that complements this object.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:video:secure_url'] = array( + 'label' => t('Open Graph Video Secure'), + 'description' => t('A URL to a video file that complements this object using the HTTPS protocol.'), 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', 'element' => array( '#theme' => 'metatag_opengraph', ), ); + $info['tags']['og:video:width'] = array( + 'label' => t('Open Graph Video Width'), + 'description' => t('The width of the video.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:video:height'] = array( + 'label' => t('Open Graph Video Height'), + 'description' => t('The height of the video.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + ); + $info['tags']['og:video:type'] = array( + 'label' => t('Open Graph Video Type'), + 'description' => t('The type of the video file.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'open-graph', + 'element' => array( + '#theme' => 'metatag_opengraph', + ), + 'form' => array( + '#type' => 'select', + '#options' => array( + 'application/x-shockwave-flash' => 'Flash - playable directly from the feed', + 'text/html' => 'Separate HTML page', + ), + '#empty_option' => t('- None -'), + ), + ); return $info; } diff --git a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module index d94e7d4551efc875270699c4c0483d5eeb5ced01..f4a191dcbfb2ecf4abfbf50092b38d1dd2ec17d5 100644 --- a/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module +++ b/sites/all/modules/metatag/metatag_opengraph/metatag_opengraph.module @@ -74,4 +74,7 @@ og:audio:type og:upc og:isbn + +fb:admins +fb:app_id */ diff --git a/sites/all/modules/metatag/metatag_twitter_cards/README.txt b/sites/all/modules/metatag/metatag_twitter_cards/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..d6e6e8290d91c64fb8f5a8a611ed352a3c5970c7 --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/README.txt @@ -0,0 +1,44 @@ +Metatag: Twitter Cards +---------------------- +This module adds the fourteen basic Twitter Cards meta tags [1]. The following +tags are provided: + +* twitter:card +* twitter:site +* twitter:creator +* twitter:url +* twitter:title +* twitter:description +* twitter:image +* twitter:image:width +* twitter:image:height +* twitter:player +* twitter:player:width +* twitter:player:height +* twitter:player:stream +* twitter:player:stream:content_type + + +Usage +------------------------------------------------------------------------------ +The Twitter Cards meta tags are configured along with all other meta tags; +on-form help is provided to aid with configuring the meta tags. + +After enabling and configuring the meta tags it is important to first test [2] +the meta tags for compliance with Twitter's standards, and then apply [3] to +have your site's usage approved. + + +Credits +------------------------------------------------------------------------------ +The initial development was by nico059 [4] with contributions by many in the +community [5]. + + +References +------------------------------------------------------------------------------ +1: https://dev.twitter.com/docs/cards +2: https://dev.twitter.com/docs/cards/preview +3: http://drupal.org/user/960720 +4: http://www.gemeentemuseum.nl/ +5: http://drupal.org/node/1664322 diff --git a/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info new file mode 100644 index 0000000000000000000000000000000000000000..670094651c573d17fdd2898decbc7ba5991c18ff --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.info @@ -0,0 +1,11 @@ +name = Meta tags: Twitter Cards +description = "Provides support for Twitter's Card meta tags. NOTE: Only use if the site supports SSL as all URLs *must* be secured via HTTPS." +package = Meta tags +core = 7.x +dependencies[] = metatag +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" +core = "7.x" +project = "metatag" +datestamp = "1364088611" + diff --git a/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.metatag.inc b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.metatag.inc new file mode 100644 index 0000000000000000000000000000000000000000..3d705a67141b7e4410897a00ef9bcb6e790e0ea2 --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.metatag.inc @@ -0,0 +1,214 @@ +<?php +/** + * @file + * Metatag integration for the metatag Twitter Cards module. + */ + +/** + * Implements hook_metatag_config_default_alter(). + */ +function metatag_twitter_cards_metatag_config_default_alter(array &$configs) { + foreach ($configs as &$config) { + switch ($config->instance) { + case 'global': + $config->config += array( + 'twitter:card' => array('value' => 'summary'), + 'twitter:description' => array('value' => '[site:slogan]'), + 'twitter:title' => array('value' => '[site:name]'), + 'twitter:url' => array('value' => '[current-page:url:absolute]'), + ); + break; + + case 'global:frontpage': + $config->config += array( + 'twitter:description' => array('value' => ''), + ); + break; + + case 'node': + $config->config += array( + 'twitter:card' => array('value' => 'summary'), + 'twitter:description' => array('value' => '[node:summary]'), + 'twitter:title' => array('value' => '[node:title]'), + ); + break; + + case 'taxonomy_term': + $config->config += array( + 'twitter:card' => array('value' => 'summary'), + 'twitter:title'=> array('value' => '[term:name]'), + ); + break; + } + } +} + +/** + * Implements hook_metatag_info(). + */ +function metatag_twitter_cards_metatag_info() { + $info['groups']['twitter-cards'] = array( + 'label' => t('Twitter card'), + 'description' => t('A set of meta tags specially for controlling the summaries displayed when content is shared on <a href="!url">Twitter</a>. <strong>NOTE:</strong> Only use if the site supports SSL as all URLs <em>must</em> be secured via HTTPS.', array('!url' => 'http://twitter.com/')), + 'form' => array( + '#weight' => 60, + ), + ); + + $info['tags']['twitter:card'] = array( + 'label' => t('Twitter card type'), + 'description' => t('Notes: no other fields are required for a <em>Summary</em> card, a <em>Photo</em> card requires the \'image\' field, while a <em>Media player</em> card requires the \'title\', \'description\', \'media player URL\', \'media player width\', \'media player height\' and \'image\' fields.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'form' => array( + '#type' => 'select', + '#options' => array( + 'summary' => t('Summary (default)'), + 'photo' => t('Photo'), + 'player' => t('Media player'), + ), + '#empty_option' => t('- None -'), + ), + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:site'] = array( + 'label' => t('Site\'s Twitter account'), + 'description' => t('The @username for the website, which will be displayed in the Card\'s footer; must include the @ symbol.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'context' => array('global'), + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:site:id'] = array( + 'label' => t('Site\'s Twitter account ID'), + 'description' => t('The numerical Twitter account ID for the website, which will be displayed in the Card\'s footer.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'context' => array('global'), + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:creator'] = array( + 'label' => t('Creator\'s Twitter account'), + 'description' => t('The @username for the content creator / author for this page, including the @ symbol.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:creator:id'] = array( + 'label' => t('Creator\'s Twitter account ID'), + 'description' => t('The numerical Twitter account ID for the content creator / author for this page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:url'] = array( + 'label' => t('Page URL'), + 'description' => t('The permalink / canonical URL of the current page.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:title'] = array( + 'label' => t('Title'), + 'description' => t('The page\'s title, which should be concise; it will be truncated at 70 characters by Twitter. This field is required unless this the \'type\' field is set to "photo".'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:description'] = array( + 'label' => t('Description'), + 'description' => t('A description that concisely summarizes the content of the page, as appropriate for presentation within a Tweet. Do not re-use the title text as the description, or use this field to describe the general services provided by the website. The string will be truncated, by Twitter, at the word to 200 characters.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:image'] = array( + 'label' => t('Image URL'), + 'description' => t('The URL to a unique image representing the content of the page. Do not use a generic image such as your website logo, author photo, or other image that spans multiple pages. Images larger than 120x120px will be resized and cropped square based on longest dimension. Images smaller than 60x60px will not be shown. If the \'type\' is set to <em>Photo</em> then the image must be at least 280x150px.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:image:width'] = array( + 'label' => t('Image width'), + 'description' => t('The width of the image being linked to, in pixels.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:image:height'] = array( + 'label' => t('Image height'), + 'description' => t('The height of the image being linked to, in pixels.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player'] = array( + 'label' => t('Media player URL'), + 'description' => t('The full URL for loading a media player. Required when using a <em>Media player</em> card.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:width'] = array( + 'label' => t('Media player width'), + 'description' => t('The width of the media player iframe, in pixels. Required when using a <em>Media player</em> card.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:height'] = array( + 'label' => t('Media player height'), + 'description' => t('The height of the media player iframe, in pixels. Required when using a <em>Media player</em> card.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:stream'] = array( + 'label' => t('MP4 media stream URL'), + 'description' => t('The full URL for an MP4 video (h.264) or audio (AAC) stream, takes precidence over the other media player field.'), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + $info['tags']['twitter:player:stream:content_type'] = array( + 'label' => t('MP4 media stream MIME type'), + 'description' => t('The MIME type for the media contained in the stream URL, as defined by <a href="!url">RFC 4337</a>.', array('!url' => 'http://tools.ietf.org/rfc/rfc4337.txt')), + 'class' => 'DrupalTextMetaTag', + 'group' => 'twitter-cards', + 'element' => array( + '#theme' => 'metatag_twitter_cards', + ), + ); + return $info; +} diff --git a/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.module b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.module new file mode 100644 index 0000000000000000000000000000000000000000..2f835c4f2b9c10ff5cf3098605548055179eff37 --- /dev/null +++ b/sites/all/modules/metatag/metatag_twitter_cards/metatag_twitter_cards.module @@ -0,0 +1,35 @@ +<?php +/** + * @file + * Primary hook implementations for Metatag: Twitter Cards. + */ + +/** + * Implements hook_ctools_plugin_api(). + */ +function metatag_twitter_cards_ctools_plugin_api($owner, $api) { + if ($owner == 'metatag' && $api == 'metatag') { + return array('version' => 1); + } +} + +/** + * Implements hook_theme(). + */ +function metatag_twitter_cards_theme() { + $info['metatag_twitter_cards'] = array( + 'render element' => 'element', + ); + + return $info; +} + +/** + * Theme callback for an twittercard meta tag. + */ +function theme_metatag_twitter_cards($variables) { + $element = &$variables['element']; + element_set_attributes($element, array('#name' => 'property', '#value' => 'content')); + unset($element['#value']); + return theme('html_tag', $variables); +} diff --git a/sites/all/modules/metatag/metatag_ui/metatag_ui.info b/sites/all/modules/metatag/metatag_ui/metatag_ui.info index 801f2c87e7b8b1485f64b21bd7ac899d4328d863..30fcb4bb214463398785feae58fdd6ccfb9b812c 100644 --- a/sites/all/modules/metatag/metatag_ui/metatag_ui.info +++ b/sites/all/modules/metatag/metatag_ui/metatag_ui.info @@ -1,14 +1,14 @@ name = Meta tag UI -description = User interface for the Meta tag API. +description = "DEPRECATED admin interface for the Meta tag API, this functionality has be merged into the main module." package = Meta tags core = 7.x dependencies[] = metatag dependencies[] = ctools hidden = TRUE -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/metatag_ui/metatag_ui.module b/sites/all/modules/metatag/metatag_ui/metatag_ui.module new file mode 100644 index 0000000000000000000000000000000000000000..0942a6b3556b0ccd63d6bd99b76bc5f1d378c569 --- /dev/null +++ b/sites/all/modules/metatag/metatag_ui/metatag_ui.module @@ -0,0 +1,5 @@ +<?php +/** + * @file + * Empty file for the deprecated Metatag UI module. + */ diff --git a/sites/all/modules/metatag/tests/metatag_test.info b/sites/all/modules/metatag/tests/metatag_test.info index 451067fbaa435575422598278653467a376c0c06..57114e4d351c557009af0bb0dfee0dc1c7de8b27 100644 --- a/sites/all/modules/metatag/tests/metatag_test.info +++ b/sites/all/modules/metatag/tests/metatag_test.info @@ -4,9 +4,9 @@ core = 7.x dependencies[] = metatag hidden = TRUE -; Information added by drupal.org packaging script on 2012-07-13 -version = "7.x-1.0-alpha6+1-dev" +; Information added by drupal.org packaging script on 2013-03-24 +version = "7.x-1.0-beta5" core = "7.x" project = "metatag" -datestamp = "1342182783" +datestamp = "1364088611" diff --git a/sites/all/modules/metatag/tests/metatag_test.metatag.inc b/sites/all/modules/metatag/tests/metatag_test.metatag.inc index bed4abc69c946c2f5afa68dd075e0f10dd02e481..7525f5aa3475f0b4f2d0ecfdae129f3d2aaba243 100644 --- a/sites/all/modules/metatag/tests/metatag_test.metatag.inc +++ b/sites/all/modules/metatag/tests/metatag_test.metatag.inc @@ -21,6 +21,7 @@ function metatag_test_metatag_config_default() { $config->disabled = FALSE; $config->config = array( 'description' => array('value' => 'Test foo description'), + 'abstract' => array('value' => 'Test foo abstract'), 'title' => array('value' => 'Test title'), 'test:foo' => array('value' => 'foobar'), ); diff --git a/sites/all/modules/module_filter/CHANGELOG.txt b/sites/all/modules/module_filter/CHANGELOG.txt index 7de283d3e20d31ed115723a8a14d890076b88626..04f924766035e9c0ac83acf7e7c1b075b3c09af5 100644 --- a/sites/all/modules/module_filter/CHANGELOG.txt +++ b/sites/all/modules/module_filter/CHANGELOG.txt @@ -1,3 +1,21 @@ +Module Filter 7.x-2.x, 2013-01-04 +--------------------------------- +by greenSkin: Fixed issue relating to row coloring when enabling/disabling + modules via switch due to recent fix for jQuery Update module. + + +Module Filter 7.x-2.x, 2012-11-27 +--------------------------------- +by greenSkin: Added functionality to show recently enabled/disabled modules. +#1710230 by littlekoala, greenSkin: Fixed On | Off buttons does not change + state with jquery_update() module active. + + +Module Filter 7.x-2.x, 2012-11-26 +--------------------------------- +by greenSkin: Added description to filter textfield on permissions page. + + Module Filter 7.x-2.x, 2012-11-04 --------------------------------- by greenSkin: Added filter to user permissions page. diff --git a/sites/all/modules/module_filter/js/module_filter_tab.js b/sites/all/modules/module_filter/js/module_filter_tab.js index 2653f977d50f8bbcdfd03dad581e2da2081659e4..b14e84239027878c9822ca001c5e5ce8c5fc64b4 100644 --- a/sites/all/modules/module_filter/js/module_filter_tab.js +++ b/sites/all/modules/module_filter/js/module_filter_tab.js @@ -5,6 +5,40 @@ Drupal.ModuleFilter.tabs = {}; Drupal.ModuleFilter.enabling = {}; Drupal.ModuleFilter.disabling = {}; +Drupal.ModuleFilter.jQueryIsNewer = function() { + if (Drupal.ModuleFilter.jQueryNewer == undefined) { + var v1parts = $.fn.jquery.split('.'); + var v2parts = new Array('1', '4', '4'); + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length == i) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } + + if (v1parts[i] == v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } + else { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } + } + + if (v1parts.length != v2parts.length) { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } + + Drupal.ModuleFilter.jQueryNewer = false; + } + return Drupal.ModuleFilter.jQueryNewer; +}; + Drupal.behaviors.moduleFilterTabs = { attach: function(context) { if (Drupal.settings.moduleFilter.tabs) { @@ -39,6 +73,14 @@ Drupal.behaviors.moduleFilterTabs = { summary += '<span>' + Drupal.t('No modules added within the last week.') + '</span>'; } break; + case 'recent': + name = Drupal.t('Recent'); + title = Drupal.t('Modules enabled/disabled within the last week.'); + if (Drupal.settings.moduleFilter.enabledCounts['recent'].total == 0) { + tabClass += ' disabled'; + summary += '<span>' + Drupal.t('No modules were enabled or disabled within the last week.') + '</span>'; + } + break; default: var $row = $('#' + id + '-package'); name = $.trim($row.text()); @@ -90,6 +132,7 @@ Drupal.behaviors.moduleFilterTabs = { moduleFilter.element.bind('moduleFilter:start', function() { moduleFilter.tabResults = { 'all-tab': { items: {}, count: 0 }, + 'recent-tab': { items: {}, count: 0 }, 'new-tab': { items: {}, count: 0 } }; @@ -113,6 +156,11 @@ Drupal.behaviors.moduleFilterTabs = { // All tab moduleFilter.tabResults['all-tab'].count++; + // Recent tab + if (item.element.hasClass('recent-module')) { + moduleFilter.tabResults['recent-tab'].count++; + } + // New tab if (item.element.hasClass('new-module')) { moduleFilter.tabResults['new-tab'].count++; @@ -123,7 +171,7 @@ Drupal.behaviors.moduleFilterTabs = { } if (Drupal.ModuleFilter.activeTab != undefined && Drupal.ModuleFilter.activeTab.id != 'all-tab') { - if ((Drupal.ModuleFilter.activeTab.id == 'new-tab' && !item.element.hasClass('new-module')) || (Drupal.ModuleFilter.activeTab.id != 'new-tab' && id != Drupal.ModuleFilter.activeTab.id)) { + if ((Drupal.ModuleFilter.activeTab.id == 'recent-tab' && !item.element.hasClass('recent-module')) || (Drupal.ModuleFilter.activeTab.id == 'new-tab' && !item.element.hasClass('new-module')) || (Drupal.ModuleFilter.activeTab.id != 'recent-tab' && Drupal.ModuleFilter.activeTab.id != 'new-tab' && id != Drupal.ModuleFilter.activeTab.id)) { // The item is not in the active tab, so hide it. item.element.addClass('js-hide'); } @@ -183,13 +231,19 @@ Drupal.behaviors.moduleFilterTabs = { $('td.checkbox div.form-item').hide(); $('td.checkbox').each(function(i) { var $cell = $(this); + var $checkbox = $(':checkbox', $cell); var $switch = $('.toggle-enable', $cell); $switch.removeClass('js-hide').click(function() { if (!$(this).hasClass('disabled')) { - $(':checkbox', $cell).click().change(); + if (Drupal.ModuleFilter.jQueryIsNewer()) { + $checkbox.click(); + } + else { + $checkbox.click().change(); + } } }); - $(':checkbox', $cell).change(function() { + $checkbox.click(function() { if (!$switch.hasClass('disabled')) { $switch.toggleClass('off'); } diff --git a/sites/all/modules/module_filter/module_filter.admin.inc b/sites/all/modules/module_filter/module_filter.admin.inc index cd7d9e2da766576bd0bb7ccd60abcf2fe8073561..11a2f6bdd8c9a43923cf35d2d65829820842d827 100644 --- a/sites/all/modules/module_filter/module_filter.admin.inc +++ b/sites/all/modules/module_filter/module_filter.admin.inc @@ -70,6 +70,12 @@ function module_filter_settings() { '#description' => t('This is purely cosmetic (at least for now). Displays a ON/OFF switch rather than a checkbox to enable/disable modules.<br /><strong>Modules will not actually be enabled/disabled until the form is saved.</strong>'), '#default_value' => variable_get('module_filter_use_switch', 1), ); + $form['tabs']['module_filter_track_recent_modules'] = array( + '#type' => 'checkbox', + '#title' => t('Track recently enabled/disabled modules'), + '#description' => t('Adds a "Recent" tab that displays modules that have been enabled or disabled with the last week.'), + '#default_value' => variable_get('module_filter_track_recent_modules', 1), + ); $form['update'] = array( '#type' => 'fieldset', diff --git a/sites/all/modules/module_filter/module_filter.info b/sites/all/modules/module_filter/module_filter.info index f77610b22ff84bbeab914acfe0d66bbba76c540e..970265281a18ac8be5ff18278224bde0fd3a6e66 100644 --- a/sites/all/modules/module_filter/module_filter.info +++ b/sites/all/modules/module_filter/module_filter.info @@ -17,9 +17,9 @@ files[] = js/module_filter_tab.js configure = admin/config/user-interface/modulefilter -; Information added by drupal.org packaging script on 2012-11-05 +; Information added by drupal.org packaging script on 2013-01-05 version = "7.x-2.x-dev" core = "7.x" project = "module_filter" -datestamp = "1352121182" +datestamp = "1357349173" diff --git a/sites/all/modules/module_filter/module_filter.install b/sites/all/modules/module_filter/module_filter.install index ff37531d700bc9da57ca6ece0d9d59e0069b4ee7..6aa3dcf20107baad462eafc78226c7aec30d3e0d 100644 --- a/sites/all/modules/module_filter/module_filter.install +++ b/sites/all/modules/module_filter/module_filter.install @@ -16,6 +16,7 @@ function module_filter_uninstall() { variable_del('module_filter_dynamic_save_position'); variable_del('module_filter_use_url_fragment'); variable_del('module_filter_use_switch'); + variable_del('module_filter_track_recent_modules'); variable_del('module_filter_remember_update_state'); } diff --git a/sites/all/modules/module_filter/module_filter.module b/sites/all/modules/module_filter/module_filter.module index 05d8ab88771aea68b7b728858c4fa234f9b12132..39ca0e7fa122b9e7e5cb5740c8faa98858f4ed4c 100644 --- a/sites/all/modules/module_filter/module_filter.module +++ b/sites/all/modules/module_filter/module_filter.module @@ -83,6 +83,10 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) $form['#theme'] = 'module_filter_system_modules'; $form['#submit'][] = 'module_filter_system_modules_submit_redirect'; + + if (variable_get('module_filter_track_recent_modules', 1)) { + $form['#submit'][] = 'module_filter_system_modules_submit_recent'; + } } /** @@ -91,6 +95,7 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) function module_filter_form_user_admin_permissions_alter(&$form, &$form_state) { $form['module_filter'] = array( '#type' => 'module_filter', + '#description' => t('Filter list by module. Use the query operator "perm" to filter by permission, e.g., perm:access.'), '#attached' => array( 'js' => array( drupal_get_path('module', 'module_filter') . '/js/permissions.js', @@ -163,6 +168,7 @@ function form_process_module_filter($element, &$form_state) { 'dynamicPosition' => (!module_exists('page_actions')) ? variable_get('module_filter_dynamic_save_position', 1) : FALSE, 'useURLFragment' => variable_get('module_filter_use_url_fragment', 1), 'useSwitch' => variable_get('module_filter_use_switch', 1), + 'trackRecent' => variable_get('module_filter_track_recent_modules', 1), 'rememberUpdateState' => variable_get('module_filter_remember_update_state', 0), ) ), @@ -171,6 +177,9 @@ function form_process_module_filter($element, &$form_state) { ) ) ); + if (isset($element['#description'])) { + $element['name']['#description'] = $element['#description']; + } if (variable_get('module_filter_remember_update_state', 0)) { $element['name']['#attached']['js'][] = 'misc/jquery.cookie.js'; } @@ -193,6 +202,20 @@ function module_filter_system_modules_submit_redirect($form, &$form_state) { ); } +function module_filter_system_modules_submit_recent($form, &$form_state) { + $recent_modules = variable_get('module_filter_recent_modules', array()); + + foreach ($form_state['values']['modules'] as $package => $modules) { + foreach ($modules as $key => $module) { + if ($form['modules'][$package][$key]['enable']['#default_value'] != $module['enable']) { + $recent_modules[$key] = REQUEST_TIME; + } + } + } + + variable_set('module_filter_recent_modules', $recent_modules); +} + function module_filter_new_modules() { // Get current list of modules. $files = system_rebuild_module_data(); @@ -222,3 +245,7 @@ function module_filter_get_id($text) { $id = preg_replace('/([^a-z0-9]+)/', '-', $id); return trim($id, '-'); } + +function module_filter_recent_filter($var) { + return (!($var < REQUEST_TIME - 60*60*24*7)); +} diff --git a/sites/all/modules/module_filter/module_filter.theme.inc b/sites/all/modules/module_filter/module_filter.theme.inc index 22f764dc6df884a4953833832cf44819159a44ca..ac7e9d9960a4188df09fe6ff4ee1aeb7e87818ce 100644 --- a/sites/all/modules/module_filter/module_filter.theme.inc +++ b/sites/all/modules/module_filter/module_filter.theme.inc @@ -76,11 +76,22 @@ function theme_module_filter_system_modules_tabs($variables) { t('Version'), t('Description') ); - $package_ids = array('all', 'new'); - $enabled['all'] = $enabled['new'] = array(); + $package_ids = array('all'); + $enabled['all'] = array(); + + if (variable_get('module_filter_track_recent_modules', 1)) { + $recent_modules = array_filter(variable_get('module_filter_recent_modules', array()), 'module_filter_recent_filter'); + // Save the filtered results. + variable_get('module_filter_recent_modules', $recent_modules); + + $package_ids[] = 'recent'; + $enabled['recent'] = array(); + } // Determine what modules are new (within a week). $new_modules = module_filter_new_modules(); + $package_ids[] = 'new'; + $enabled['new'] = array(); $rows = array(); $flip = array('even' => 'odd', 'odd' => 'even'); @@ -99,6 +110,9 @@ function theme_module_filter_system_modules_tabs($variables) { $is_enabled = isset($module['enable']['#default_value']) ? $module['enable']['#default_value'] : ''; $enabled['all'][] = $enabled[$package_id][] = $is_enabled; + if (isset($recent_modules[$key])) { + $enabled['recent'][] = $is_enabled; + } if (isset($new_modules[$key])) { $enabled['new'][] = $is_enabled; } @@ -136,6 +150,9 @@ function theme_module_filter_system_modules_tabs($variables) { $row[] = array('data' => $description, 'class' => array('description')); $class = array(module_filter_get_id($package) . '-tab', 'module', $stripe); + if (isset($recent_modules[$key])) { + $class[] = 'recent-module'; + } if (isset($new_modules[$key])) { $class[] = 'new-module'; } diff --git a/sites/all/modules/options_element/options_element.inc b/sites/all/modules/options_element/options_element.inc index d6a90cc2e0c290ab96162a74c26edfacb2d03930..ed64743503c62a51bfe991da6e8cdd43452e09f0 100644 --- a/sites/all/modules/options_element/options_element.inc +++ b/sites/all/modules/options_element/options_element.inc @@ -133,13 +133,13 @@ function _form_options_expand($element) { '#rows' => 5, '#required' => isset($element['#required']) ? $element['#required'] : FALSE, '#description' => t('List options one option per line.'), - '#attributes' => $element['#disabled'] ? array('readonly' => 'readonly') : array(), + '#attributes' => $element['#options_readonly'] ? array('readonly' => 'readonly') : array(), '#wysiwyg' => FALSE, // Prevent CKeditor from trying to hijack. ); // If validation fails, reload the user's text even if it's not valid. - if (isset($element['#value']['text'])) { - $element['options_field']['#value'] = $element['#value']['text']; + if (isset($element['#value']['options_text'])) { + $element['options_field']['#value'] = $element['#value']['options_text']; } // Most of the time, we'll be converting the options array into the text. else { @@ -195,7 +195,8 @@ function _form_options_expand($element) { * @see form_options_validate() */ function _form_options_validate($element, &$form_state) { - // Convert text to an array of options. + // Even though we already have the converted options in #value['options'], run + // the conversion again to check for duplicates in the user-defined list. $duplicates = array(); $options = form_options_from_text($element['#value']['options_text'], $element['#key_type'], empty($element['#optgroups']), $duplicates); diff --git a/sites/all/modules/options_element/options_element.info b/sites/all/modules/options_element/options_element.info index d370e45105f5455b32b48763475e679a138cea55..9f566f94e64bfe51cc735166859659ee9373019d 100644 --- a/sites/all/modules/options_element/options_element.info +++ b/sites/all/modules/options_element/options_element.info @@ -2,9 +2,9 @@ name = Options element description = A custom form element for entering the options in select lists, radios, or checkboxes. core = 7.x -; Information added by drupal.org packaging script on 2012-03-17 -version = "7.x-1.7" +; Information added by drupal.org packaging script on 2012-09-13 +version = "7.x-1.8" core = "7.x" project = "options_element" -datestamp = "1332018945" +datestamp = "1347551745" diff --git a/sites/all/modules/options_element/options_element.js b/sites/all/modules/options_element/options_element.js index 0d71ae7fc76ea453e2cc77e560369cae7146195b..11ccb6a78820a14573537141870b2bc26e66e4e8 100644 --- a/sites/all/modules/options_element/options_element.js +++ b/sites/all/modules/options_element/options_element.js @@ -37,7 +37,8 @@ Drupal.optionsElement = function(element) { this.keyType = element.className.replace(/^.*?options-key-type-([a-z]+).*?$/, '$1'); this.customKeys = Boolean(element.className.match(/options-key-custom/)); this.identifier = this.manualOptionsElement.id + '-widget'; - this.enabled = $(this.manualOptionsElement).attr('readonly') == ''; + // jQuery 1.6 API change: http://api.jquery.com/prop/ + this.enabled = $.fn.prop ? !$(this.manualOptionsElement).prop('readonly') : !$(this.manualOptionsElement).attr('readonly'); this.defaultValuePattern = $(element).find('input.default-value-pattern').val(); if (this.defaultValuePattern) { diff --git a/sites/all/modules/options_element/options_element.module b/sites/all/modules/options_element/options_element.module index db007de545e2d5b186770925a770bb8bd0730cf7..d777164bca1495c07d6d4257065cc0343483fdba 100644 --- a/sites/all/modules/options_element/options_element.module +++ b/sites/all/modules/options_element/options_element.module @@ -12,7 +12,7 @@ * * The 'options' form element type is useful when collecting a series of * values in a list. The values within the list may optionally have unique - * keys, such as that in a array structure. In addition, a default choice + * keys, such as that in an array structure. In addition, a default choice * (or several default choices) may be selected by the user. * * @code @@ -89,13 +89,13 @@ function options_element_element_info() { '#optgroups' => TRUE, '#multiple' => FALSE, '#options' => array(), + '#options_readonly' => FALSE, '#key_type' => 'mixed', '#key_type_toggle' => NULL, '#key_type_toggled' => FALSE, '#default_value_allowed' => TRUE, '#default_value_pattern' => '', '#element_validate' => array('form_options_validate'), - '#disabled' => FALSE, ); return $type; diff --git a/sites/all/modules/pagepreview/LICENSE.txt b/sites/all/modules/pagepreview/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/pagepreview/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/pagepreview/pagepreview.css b/sites/all/modules/pagepreview/pagepreview.css new file mode 100644 index 0000000000000000000000000000000000000000..a780d28cd2430c3f9d511a705f7601cac3688145 --- /dev/null +++ b/sites/all/modules/pagepreview/pagepreview.css @@ -0,0 +1,6 @@ +iframe.pagepreview-preview { + border: 1px solid #aaa; + width: 100%; + height: 480px; +} + diff --git a/sites/all/modules/pagepreview/pagepreview.info b/sites/all/modules/pagepreview/pagepreview.info index 6243f29e6c646eb03ff89bb3987bd51fc4d76ff8..cce5063dce4a656420aa76be1b758aad7b54f3a8 100644 --- a/sites/all/modules/pagepreview/pagepreview.info +++ b/sites/all/modules/pagepreview/pagepreview.info @@ -1,4 +1,10 @@ -; $Id$ name = Page Preview description = Allows node previews to be viewed as fully-rendered pages. core = 7.x + +; Information added by drupal.org packaging script on 2012-11-06 +version = "7.x-1.x-dev" +core = "7.x" +project = "pagepreview" +datestamp = "1352208035" + diff --git a/sites/all/modules/pagepreview/pagepreview.install b/sites/all/modules/pagepreview/pagepreview.install index 7e614c76410fba2eb92442623e2fa0111464fbb0..8a9fadd6b5306e96cc60f734c93732f9e50c1216 100644 --- a/sites/all/modules/pagepreview/pagepreview.install +++ b/sites/all/modules/pagepreview/pagepreview.install @@ -1,21 +1,17 @@ <?php -// $Id$ - /** - * @file - * Install functions for the Page Preview module. + * Implementation of hook_schema(). */ +function pagepreview_schema() { + $schema['cache_pagepreview'] = drupal_get_schema_unprocessed('system', 'cache'); + + return $schema; +} /** - * Implements hook_install(). - * - * Set module weight to so that pagepreview_init() can run early. + * Create a dedicated cache table. */ -function pagepreview_install() { - db_update('system') - ->fields(array( - 'weight' => -1, - )) - ->condition('name', 'pagepreview', '=') - ->execute(); +function pagepreview_update_7100() { + $schema = pagepreview_schema(); + db_create_table('cache_pagepreview', $schema['cache_pagepreview']); } diff --git a/sites/all/modules/pagepreview/pagepreview.module b/sites/all/modules/pagepreview/pagepreview.module index 5f9a274dbc57e56d6e4c440ccc5b0d37e9b15aa5..1fa69197217e6fe974a114c5961050989bc535e3 100644 --- a/sites/all/modules/pagepreview/pagepreview.module +++ b/sites/all/modules/pagepreview/pagepreview.module @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -13,7 +12,7 @@ function pagepreview_menu() { $items['pagepreview/%'] = array( 'title' => 'Page Preview', 'description' => 'Menu callback for rendering a full-page preview.', - 'page callback' => 'pagepreview_get_preview', + 'page callback' => 'pagepreview_deliver_page', 'page arguments' => array(1), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, @@ -27,7 +26,8 @@ function pagepreview_menu() { * Replaces the default submit callback for the "Preview" button. */ function pagepreview_form_alter(&$form, &$form_state, $form_id) { - if ($form['#id'] == 'page-node-form') { + if (!empty($form['#node_edit_form'])) { + drupal_add_css(drupal_get_path('module', 'pagepreview') . '/pagepreview.css'); if ($form['actions']['preview']) { $form['actions']['preview']['#submit'] = array('pagepreview_node_form_build_preview'); } @@ -42,7 +42,7 @@ function pagepreview_node_form_build_preview($form, &$form_state) { $node->pagepreview = TRUE; // Get the expected URL alias from Pathauto, if applicable. - if (module_exists('pathauto') && $node->path['pathauto']) { + if (module_exists('pathauto') && (!isset($node->path['pathauto']) || $node->path['pathauto'])) { module_load_include('inc', 'pathauto'); $src = "node/" . $node->nid; $alias = pathauto_create_alias('node', 'return', $src, array('node' => $node), $node->type, $node->language); @@ -52,61 +52,31 @@ function pagepreview_node_form_build_preview($form, &$form_state) { } // Cache the temporary node and active the preview area of the node form. - cache_set('pagepreview:' . $form['form_token']['#default_value'], $node, 'cache_page', CACHE_TEMPORARY); + $preview_id = md5(json_encode($form_state['values']) . mt_rand()); + cache_set($preview_id, $node, 'cache_pagepreview', CACHE_TEMPORARY); - $form_state['node_preview'] = '<iframe class="pagepreview-preview" style="width:100%; height: 480px;" src="' . base_path() . 'pagepreview/' . $form['#token'] . '"></iframe>'; + $form_state['node_preview'] = '<iframe class="pagepreview-preview" src="' . base_path() . 'pagepreview/' . $preview_id . '"></iframe>'; $form_state['rebuild'] = TRUE; } -/** - * Implements hook_init(). - * - * We need drupal_is_front_page() to reflect the page being previewed, not the - * path of the pagepreview callback. Since the result of drupal_is_front_page() - * is statically cached, we have to call it first and trick it. - */ -function pagepreview_init() { - // We only want to do this on pagepreview requests. - $path = $_GET['q']; - $parts = explode('/', $path); - if ($parts[0] == 'pagepreview' && isset($parts[1])) { - // Get the cached temporary node. - $form_token = drupal_get_token($parts[1]); - $cache = cache_get('pagepreview:' . $form_token, 'cache_page'); - - // If we can't find a cached node, might as well quit here. - if (!$cache) { - print t('There was a problem generating the preview. Please review the form for error and try again.'); - exit; - } - - // Switch $_GET['q'] to the expected path, call drupal_is_front_page() to - // set the static cache, the switch $_GET['q'] back to the original. - $node = $cache->data; - if ($node->nid) { - $_GET['q'] = 'node/' . $node->nid; - } - drupal_is_front_page(); - $_GET['q'] = $path; - - // Meanwhile, don't allow the preview result to be cached. - $GLOBALS['conf']['cache'] = FALSE; - } -} - /** * Menu callback for "pagepreview/%". * * Directly prints a rendered page based on the cached temporary node. * - * @param $token - * The value of $form['#token']. This is generally the form ID. + * @param string $preview_id + * The cache key for the object to be retrieved. */ -function pagepreview_get_preview($token) { +function pagepreview_deliver_page($preview_id) { // Get the cached temporary node. - $form_token = drupal_get_token($token); - $cache = cache_get('pagepreview:' . $form_token, 'cache_page'); - $node = $cache->data; + $node = pagepreview_cache_get($preview_id); + + // If we don't have a valid node for whatever reason, quit here. + if (!$node) { + drupal_exit(); + } + + // Change the preview's page title. drupal_set_title($node->title); // Overrides $_GET['q'] so that other elements on the page can react to the @@ -117,26 +87,44 @@ function pagepreview_get_preview($token) { elseif (!empty($node->path['alias'])) { $_GET['q'] = trim($node->path['alias'], '/'); } - elseif (!empty($node->path['old_alias'])) { - $_GET['q'] = trim($node->path['old_alias'], '/'); - } - $preview = pagepreview_render_preview($node); + // Allow other modules to alter the node or execute code before building the + // preview. + drupal_alter('pagepreview_node', $node); + + $preview = pagepreview_build_preview($node); // Switch to the anonymous user for page rendering. // TODO: make this configurable. global $user; - $original_user = $user; drupal_save_session(FALSE); $user = user_load(0); // Suppress fancy stuff like admin and admin_menu.module for the preview. module_invoke_all('suppress'); - drupal_deliver_page(drupal_render($preview)); + // Add JS within the preview to prevent clicking through to links. + drupal_add_js(drupal_get_path('module', 'pagepreview') . '/pagepreview.preview.js'); + + // Deliver the page output. + drupal_deliver_page($preview); - $user = $original_user; - drupal_save_session(TRUE); + // Remove the cached preview. + cache_clear_all($preview_id, 'cache_pagepreview'); +} + +/** + * Return a preview object from cache. + * + * @param string $preview_id + * The cache key for the object to be retrieved. + * + * @return object + * The cached object, or FALSE if none was found. + */ +function pagepreview_cache_get($preview_id) { + $cache = cache_get($preview_id, 'cache_pagepreview'); + return ($cache) ? $cache->data : FALSE; } /** @@ -147,7 +135,7 @@ function pagepreview_get_preview($token) { * * @see theme_node_preview() */ -function pagepreview_render_preview($node) { +function pagepreview_build_preview($node) { if (node_access('create', $node) || node_access('update', $node)) { _field_invoke_multiple('load', 'node', array($node->nid => $node)); // Load the user's name when needed. @@ -177,15 +165,26 @@ function pagepreview_render_preview($node) { $node->in_preview = TRUE; // If enabled, allow page_manager.module to handle node rendering. if (module_exists('page_manager')) { - module_load_include('inc', 'page_manager', 'plugins/tasks/node_view'); - $output = page_manager_node_view_page($node); + // Load my task plugin + $task = page_manager_get_task('node_view'); + + // Load the node into a context. + ctools_include('context'); + ctools_include('context-task-handler'); + $contexts = ctools_context_handler_get_task_contexts($task, '', array($node)); + + $output = ctools_context_handler_render($task, '', $contexts, array($node->nid)); + // Page manager is not handeling node_view + if ($output === FALSE) { + $output = node_view($node, 'full'); + } } else { $output = node_view($node, 'full'); } unset($node->in_preview); // Rather than the default t('Preview') allow user to see more acurate rendering - drupal_set_title($node->title, PASS_THROUGH); + drupal_set_title($node->title); } return $output; diff --git a/sites/all/modules/pagepreview/pagepreview.preview.js b/sites/all/modules/pagepreview/pagepreview.preview.js new file mode 100644 index 0000000000000000000000000000000000000000..4f56037dfb6b8d352271956dd336b7cd8329f39a --- /dev/null +++ b/sites/all/modules/pagepreview/pagepreview.preview.js @@ -0,0 +1,19 @@ +(function($){ + // Prevent links, forms, and inputs from unloading the preview page. + $(document).delegate('a', 'click', function(ev){ + ev.preventDefault(); + return false; + }); + $(document).delegate('form', 'submit', function(ev){ + ev.preventDefault(); + return false; + }); + $(document).delegate('input', 'mousedown', function(ev){ + ev.preventDefault(); + return false; + }); + $(document).delegate('input', 'click', function(ev){ + ev.preventDefault(); + return false; + }); +})(jQuery); \ No newline at end of file diff --git a/sites/all/modules/picture/LICENSE.txt b/sites/all/modules/picture/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/picture/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/picture/README.txt b/sites/all/modules/picture/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..2fafe5c6a058e3934f909b36ddfc6a9b72a6c9f3 --- /dev/null +++ b/sites/all/modules/picture/README.txt @@ -0,0 +1,3 @@ +-- SUMMARY -- + +Picture element \ No newline at end of file diff --git a/sites/all/modules/picture/flexslider_picture/flexslider_picture.info b/sites/all/modules/picture/flexslider_picture/flexslider_picture.info new file mode 100644 index 0000000000000000000000000000000000000000..62c819cbc63748ddf4d85fc336ba3dbe28920d0e --- /dev/null +++ b/sites/all/modules/picture/flexslider_picture/flexslider_picture.info @@ -0,0 +1,15 @@ +name = FlexSlider Picture +description = Integrates the Picture module with the FlexSlider module for a truly responsive slider. +package = Picture +core = 7.x + +dependencies[] = picture +dependencies[] = flexslider +dependencies[] = flexslider_fields + +; Information added by drupal.org packaging script on 2012-12-13 +version = "7.x-1.1" +core = "7.x" +project = "picture" +datestamp = "1355413446" + diff --git a/sites/all/modules/picture/flexslider_picture/flexslider_picture.install b/sites/all/modules/picture/flexslider_picture/flexslider_picture.install new file mode 100644 index 0000000000000000000000000000000000000000..5c7f214b2f55adf7bf91d1c98a1e7d77dfa62782 --- /dev/null +++ b/sites/all/modules/picture/flexslider_picture/flexslider_picture.install @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Install, update and schema hooks for the FlexSlider Picture module. + */ +function flexslider_picture_schema() { + $schema = array(); + $schema['flexslider_picture_optionset'] = array( + 'description' => 'Saves which flexslider optionsets use picture mappings and which use image styles.', + 'export' => array(), + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier.', + 'no export' => TRUE, // Do not export database-only keys. + ), + 'flexslider_optionset' => array( + 'description' => 'The machine-readable option set name.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'imagestyle_type' => array( + 'description' => 'One of image_style or picture_mapping.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'mapping' => array( + 'description' => 'The picture mapping for this optionset.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'primary key' => array('id'), + 'indexes' => array( + 'imagestyle_type' => array('imagestyle_type'), + ), + ); + return $schema; +} + +/** + * Implements hook_schema_alter(). + */ +function flexslider_picture_schema_alter(&$schema) { + $schema['flexslider_optionset']['join']['flexslider_picture'] = array( + 'table' => 'flexslider_picture_optionset', + 'left_key' => 'name', + 'right_key' => 'flexslider_optionset', + 'callback' => 'flexslider_picture_join_callback', + 'load' => array( + 'imagestyle_type', + 'mapping', + ), + ); +} diff --git a/sites/all/modules/picture/flexslider_picture/flexslider_picture.module b/sites/all/modules/picture/flexslider_picture/flexslider_picture.module new file mode 100644 index 0000000000000000000000000000000000000000..0830e48593c9fba62dfa12ef3404a4eccf5c2140 --- /dev/null +++ b/sites/all/modules/picture/flexslider_picture/flexslider_picture.module @@ -0,0 +1,156 @@ +<?php + +/** + * @file + * + */ + +/** + * Implements hook_theme_registry_alter(). + */ +function flexslider_picture_theme_registry_alter(&$registry) { + $registry['flexslider_list']['file'] = 'flexslider_picture.theme.inc'; + $registry['flexslider_list']['theme_path'] = drupal_get_path('module', 'flexslider_picture') . '/theme'; + $registry['flexslider_list']['function'] = 'theme_flexslider_picture_list'; + $registry['flexslider_list']['includes'][] = $registry['flexslider_list']['theme_path'] . '/' . $registry['flexslider_list']['file']; +} + +/** + * Implements hook_field_formatter_info(). + * + * Redefine the formatter from flexslider so that our formatter function will + * be used. + */ +function flexslider_picture_field_formatter_info() { + return array( + 'flexslider' => array( + 'label' => t('flexslider'), + 'field types' => array('image', 'media'), + 'settings' => array( + 'optionset' => 'default', + 'flexslider_reference_img_src' => NULL, + ), + ), + ); +} + +/** + * Implements hook_field_formatter_settings_form(). + * + * Provides display settings form within the manage display page of + * an image field with formatter flexslider. + */ +function flexslider_picture_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + return flexslider_fields_field_formatter_settings_form($field, $instance, $view_mode, $form, $form_state); +} + +/** + * Implements hook_field_formatter_settings_summary(). + * + * Displays the summary of the set options of a flexslider formatted image field + */ +function flexslider_picture_field_formatter_settings_summary($field, $instance, $view_mode) { + return flexslider_fields_field_formatter_settings_summary($field, $instance, $view_mode); +} + +/** + * Implements hook_field_formatter_view(). + * + * Prepares a renderable array of images and adds the neccessary JS and CSS + */ +function flexslider_picture_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = flexslider_fields_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display); + $optionsets = array(); + foreach (element_children($element) as $child) { + if (!isset($optionsets[$element[$child]['#settings']['optionset']])) { + $optionsets[$element[$child]['#settings']['optionset']] = flexslider_optionset_load($element[$child]['#settings']['optionset']); + } + $optionset = $optionsets[$element[$child]['#settings']['optionset']]; + if ($optionset && isset($optionset->imagestyle_type) && !empty($optionset->imagestyle_type) && $optionset->imagestyle_type == 'picture_mapping') { + $element[$child]['#attached'] = array( + 'library' => array( + array('picture', 'matchmedia'), + array('picture', 'picturefill'), + array('picture', 'picture.ajax'), + ), + ); + } + } + return $element; +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function flexslider_picture_form_ctools_export_ui_edit_item_form_alter(&$form, &$form_state) { + if ($form_state['plugin']['schema'] == 'flexslider_optionset') { + $form['image_style']['imagestyle_type'] = array( + '#type' => 'radios', + '#title' => t('Image type'), + '#description' => t( + 'Choosing picture mapping will give you a truly responsive slider. + Flexslider is responsive because it will resize the images for smaller screens, but it will still load the large image. + With picture mappings, a smaller image will be loaded for smaller screens (and thus less data transfer).'), + '#options' => array( + 'image_style' => t('Image Style'), + 'picture_mapping' => t('Picture Mapping'), + ), + '#weight' => -2, + '#default_value' => (isset($form_state['item']->imagestyle_type) && !empty($form_state['item']->imagestyle_type)) ? $form_state['item']->imagestyle_type : 'image_style', + ); + + $form['image_style']['normal']['#description'] .= ' ' . t('If you selected Picture Mapping above, this image style will be used as a fallback.'); + + $options = picture_get_mapping_options(); + $form['image_style']['mapping'] = array( + '#title' => t('Normal picuture mapping'), + '#states' => array( + 'visible' => array( + ':input[name="image_style[imagestyle_type]"]' => array('value' => 'picture_mapping'), + ) + ), + '#weight' => -1, + ); + if (!empty($options)) { + $form['image_style']['mapping'] += array( + '#type' => 'select', + '#description' => t('Picture mapping for the main stage images.'), + '#options' => $options, + ); + } + else { + $form['image_style']['mapping'] += array( + '#type' => 'item', + '#markup' => t('There\'re no picture mappings defined, you\'ll have to !create them first.', array('!create' => l(t('create'), 'admin/config/media/picture'))), + ); + } + + $form['#submit'][] = '_flexslider_picture_ctools_export_ui_edit_item_form_submit'; + } +} + +/** + * Submit callback. + */ +function _flexslider_picture_ctools_export_ui_edit_item_form_submit($form, &$form_state) { + $fields = array_intersect_key($form_state['values']['image_style'], drupal_map_assoc(array('imagestyle_type', 'mapping'))); + $fields['flexslider_optionset'] = $form_state['values']['name']; + $q = db_merge('flexslider_picture_optionset') + ->key(array('flexslider_optionset' => $form_state['values']['name'])) + ->fields($fields); + $q->execute(); +} + +/** + * Join callback. + * @see flexslider_picture_schema_alter() + */ +function flexslider_picture_join_callback(&$query, $schema, $join_schema) { + $tables = &$query->getTables(); + foreach ($tables as &$table) { + if ($table['table'] == 'flexslider_picture_optionset') { + $table['join type'] = 'LEFT OUTER'; + break; + } + } +} diff --git a/sites/all/modules/picture/flexslider_picture/theme/flexslider_picture.theme.inc b/sites/all/modules/picture/flexslider_picture/theme/flexslider_picture.theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..21cee8f71436efbd1e5a76c41379b2b727bad4f6 --- /dev/null +++ b/sites/all/modules/picture/flexslider_picture/theme/flexslider_picture.theme.inc @@ -0,0 +1,63 @@ +<?php + +/** + * Theme callback. + */ +function theme_flexslider_picture_list(&$vars) { + $optionset = &$vars['settings']['optionset']; + if (isset($optionset->imagestyle_type) && !empty($optionset->imagestyle_type) && $optionset->imagestyle_type == 'picture_mapping') { + // Reference configuration variables + $items = &$vars['items']; + $attributes = &$vars['settings']['attributes']; + $type = &$vars['settings']['type']; + $output = ''; + + // Get the breakpoints based on the mapping + $breakpoint_styles = array(); + $group_name = $optionset->mapping; + $mappings = picture_mapping_load($group_name); + if ($mappings) { + foreach ($mappings->mapping as $breakpoint_name => $multipliers) { + if (!empty($multipliers)) { + foreach ($multipliers as $multiplier => $image_style) { + if (!empty($image_style)) { + if (!isset($breakpoint_styles[$breakpoint_name])) { + $breakpoint_styles[$breakpoint_name] = array(); + } + $breakpoint_styles[$breakpoint_name][$multiplier] = $image_style; + } + } + } + } + } + // Build the list + if (!empty($items)) { + $output .= "<$type" . drupal_attributes($attributes) . '>'; + foreach ($items as $i => $item) { + // If the slide hasn't been set, build the slide using the image + // attributes given (assumes we're using a multi-image field) + // @todo need to allow for different types of field and collection fields + if (!isset($item['slide'])) { + $picture_options = array( + 'style_name' => $optionset->imagestyle_normal, + 'uri' => $item['uri'], + 'height' => $item['height'], + 'width' => $item['width'], + 'alt' => $item['alt'], + 'title' => $item['title'], + 'breakpoints' => $breakpoint_styles, + ); + } + $output .= theme('flexslider_list_item', array( + 'item' => isset($item['slide']) ? $item['slide'] : theme('picture', $picture_options), + 'thumb' => isset($item['thumb']) ? $item['thumb'] : image_style_url($optionset->imagestyle_thumbnail, $item['uri']), + 'optionset' => $optionset, + )); + } + $output .= "</$type>"; + } + + return $output; + } + return theme_flexslider_list($vars); +} \ No newline at end of file diff --git a/sites/all/modules/picture/picture.admin.inc b/sites/all/modules/picture/picture.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..70bbd1d01b428bc108cc3673ce8e1973909ee812 --- /dev/null +++ b/sites/all/modules/picture/picture.admin.inc @@ -0,0 +1,201 @@ +<?php + +/** + * @file + * Picture - map breakpoints and image styles + */ + +/** + * Admin form + */ +function picture_admin_breakpoints($form, &$form_state, $breakpoint_group_name = '') { + // Show a list of all groups if no group name is given. + if ($breakpoint_group_name == '' || $breakpoint_group_name == 'global') { + return picture_admin_breakpoints_overview_page(); + } + $machine_name = $breakpoint_group_name; + $form = array(); + + $mappings = picture_mapping_load($breakpoint_group_name); + $mappings = $mappings ? $mappings : new stdClass(); + + $form['picture_mapping'] = array( + '#type' => 'container', + '#tree' => TRUE, + ); + $form['picture_mapping']['machine_name'] = array( + '#type' => 'value', + '#value' => isset($mappings->machine_name) ? $mappings->machine_name : $machine_name, + ); + $form['picture_mapping']['breakpoint_group'] = array( + '#type' => 'value', + '#value' => isset($mappings->breakpoint_group) ? $mappings->breakpoint_group : $breakpoint_group_name, + ); + if (isset($mappings->id)) { + $form['picture_mapping']['id'] = array( + '#type' => 'value', + '#value' => $mappings->id, + ); + } + + $breakpoints = array(); + $breakpoint_group = breakpoints_breakpoint_group_load($breakpoint_group_name); + $weight = 0; + foreach ($breakpoint_group->breakpoints as $breakpoint_name) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint_name); + if ($breakpoint && $breakpoint->status) { + $breakpoint->global_weight = $breakpoint->weight; + $breakpoint->weight = $weight++; + $breakpoints[$breakpoint_name] = $breakpoint; + } + } + + $image_styles = image_style_options(TRUE); + foreach ($breakpoints as $breakpoint_name => $breakpoint) { + $label = '1x ' . $breakpoint->name . ' [' . $breakpoint->breakpoint . ']'; + $form['picture_mapping']['mapping'][$breakpoint_name]['1x'] = array( + '#title' => check_plain($label), + '#type' => 'select', + '#options' => $image_styles, + '#default_value' => isset($mappings->mapping[$breakpoint_name]['1x']) ? $mappings->mapping[$breakpoint_name]['1x'] : '', + ); + if (isset($breakpoint->multipliers) && !empty($breakpoint->multipliers)) { + foreach ($breakpoint->multipliers as $multiplier => $status) { + if ($status) { + $label = $multiplier . ' ' . $breakpoint->name . ' [' . $breakpoint->breakpoint . ']'; + $form['picture_mapping']['mapping'][$breakpoint_name][$multiplier] = array( + '#title' => check_plain($label), + '#type' => 'select', + '#options' => $image_styles, + '#default_value' => isset($mappings->mapping[$breakpoint_name][$multiplier]) ? $mappings->mapping[$breakpoint_name][$multiplier] : '', + ); + } + } + } + } + + // Buttons + $form['buttons'] = array( + '#type' => 'container', + ); + + // Submit button + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + return $form; +} + +/** + * Admin form overview page. + */ +function picture_admin_breakpoints_overview_page() { + $links = array(); + $breakpoint_groups = breakpoints_breakpoint_group_load_all(); + foreach ($breakpoint_groups as $breakpoint_group) { + $links[] = l($breakpoint_group->name, 'admin/config/media/picture/groups/' . $breakpoint_group->machine_name); + } + if (!empty($links)) { + return array( + '#type' => 'container', + '#theme' => 'item_list', + '#items' => $links, + ); + } + else { + $item['info'] = array( + '#type' => 'markup', + '#title' => t('No breakpoint groups found.'), + '#markup' => t('There\'re no breakpoint groups defined, you\'ll have to !create them first.', array('!create' => l(t('create'), 'admin/config/media/breakpoints/groups/add'))), + ); + return $item; + } +} + +/** + * Admin form submit. + */ +function picture_admin_breakpoints_submit($form, &$form_state) { + $mapping = (object)$form_state['values']['picture_mapping']; + $saved = picture_mapping_save($mapping); + $group = breakpoints_breakpoint_group_load($mapping->breakpoint_group); + if ($saved !== FALSE) { + drupal_set_message(t('Picture mappings for @group were saved.', array('@group' => $group->name))); + } + else { + drupal_set_message(t('Something went wrong while trying to save picture mappings for @group', array('@group' => $group->name)), 'error'); + } +} + +function picture_admin_export_form($form, &$form_state, $mappings_name) { + // Create the export code textarea. + ctools_include('export'); + $mapping = picture_mapping_load($mappings_name); + if (!$mapping) { + $mapping = new stdClass(); + } + $export = ctools_export_object('picture_mapping', $mapping); + $form['mapping_export'] = array( + '#type' => 'textarea', + '#title' => t('Mapping code'), + '#rows' => count(explode("\n", $export)), + '#default_value' => $export, + '#weight' => -1, + '#description' => t('<strong>Warning!</strong> Only import these mappings if the breakpoint group below has been imported on that site already, or if they were manually created there.'), + ); + + // Also export the group it belongs to. + module_load_include('inc', 'breakpoints', 'breakpoints.admin'); + $form += drupal_get_form('breakpoints_admin_breakpoint_group_export_form', $mapping->breakpoint_group); + $form['export']['#description'] = t('If you want to import this mapping on an other site, + you\'ll need to import the breakpoint group with its breakpoints as well, + if it doesn\'t already exist on that site.'); + return $form; +} + +function picture_admin_import_form($form, &$form_state) { + $form['import'] = array( + '#type' => 'textarea', + '#rows' => 10, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import') + ); + return $form; +} + + +/** + * Validate a mapping import. + */ +function picture_admin_import_form_validate($form, &$form_state) { + ctools_include('export'); + $code = $form_state['values']['import']; + $mapping = ctools_export_crud_import('picture_mapping', $code); + if (!picture_mapping_validate($mapping)) { + form_set_error('import', t('Not a valid mapping object')); + return; + } + if (picture_mapping_load($mapping->machine_name)) { + form_set_error('import', t('A mapping with machine name %name already exists', array('%name' => $mapping->machine_name))); + return; + } + form_set_value($form['import'], $mapping, $form_state); +} + +/** + * Import mapping. + */ +function picture_admin_import_form_submit($form, &$form_state) { + $mapping = $form_state['values']['import']; + if (picture_mapping_save($mapping)) { + drupal_set_message(t('Mapping %mapping saved.', array('%mapping' => $mapping->machine_name))); + $form_state['redirect'] = 'admin/config/media/picture/'; + } + else { + drupal_set_message(t('Something went wrong, we could not save the mapping'), 'error'); + } +} diff --git a/sites/all/modules/picture/picture.info b/sites/all/modules/picture/picture.info new file mode 100644 index 0000000000000000000000000000000000000000..e7cc3cfd93e0d808860067221a51a4da116da4e6 --- /dev/null +++ b/sites/all/modules/picture/picture.info @@ -0,0 +1,13 @@ +name = Picture +description = Picture element +core = 7.x +dependencies[] = breakpoints +configure = admin/config/media/picture +package = Picture + +; Information added by drupal.org packaging script on 2012-12-13 +version = "7.x-1.1" +core = "7.x" +project = "picture" +datestamp = "1355413446" + diff --git a/sites/all/modules/picture/picture.install b/sites/all/modules/picture/picture.install new file mode 100644 index 0000000000000000000000000000000000000000..a8c0071276d28a0857a579bf4953de1426631db6 --- /dev/null +++ b/sites/all/modules/picture/picture.install @@ -0,0 +1,58 @@ +<?php +/** + * @file + * Install/schema hooks for the picture module. + */ +/** + * Implements hook_schema(). + */ +function picture_schema() { + $schema = array(); + $schema['picture_mapping'] = array( + 'description' => 'Responsible images and styles mappings to breakpoints', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier for this mapping', + 'no export' => TRUE, // do not export database only keys. + ), + 'machine_name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The machine name of the mapping', + ), + 'breakpoint_group' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The group this mapping belongs to', + ), + 'mapping' => array( + 'type' => 'blob', + 'not null' => TRUE, + 'description' => 'The mappings linked to the breakpoints group', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('id'), + // CTools exportable object definition + 'export' => array( + 'key' => 'machine_name', + 'key name' => 'machine_name', + 'primary key' => 'id', + 'identifier' => 'picture_mapping', + 'admin_title' => 'label', + 'default hook' => 'default_picture_mapping', + 'api' => array( + 'owner' => 'picture', + 'api' => 'default_picture_mapping', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + ); + + return $schema; +} diff --git a/sites/all/modules/picture/picture.js b/sites/all/modules/picture/picture.js new file mode 100644 index 0000000000000000000000000000000000000000..6b638952b0f3a67d877ecea171606666a35e9433 --- /dev/null +++ b/sites/all/modules/picture/picture.js @@ -0,0 +1,10 @@ +if (Drupal && jQuery) { + // only load if Drupal and jQuery are defined. + (function ($) { + Drupal.behaviors.picture = { + attach: function (context) { + window.picturefill(context); + } + }; + })(jQuery); +} \ No newline at end of file diff --git a/sites/all/modules/picture/picture.module b/sites/all/modules/picture/picture.module new file mode 100644 index 0000000000000000000000000000000000000000..635f410c77af26138910aeb5f1db115984a6a472 --- /dev/null +++ b/sites/all/modules/picture/picture.module @@ -0,0 +1,725 @@ +<?php + +define('PICTURE_CLASS', 'picture'); +define('PICTURE_SEPARATOR', '__'); + +/** + * Implements hook_permission(). + */ +function picture_permission() { + return array( + 'administer pictures' => array( + 'title' => t('Administer Pictures'), + 'description' => t('Administer Pictures'), + ), + ); +} + +/** + * Implements hook_menu(). + */ +function picture_menu() { + $items = array(); + + $items['admin/config/media/picture'] = array( + 'title' => 'Picture', + 'description' => 'Manage Pictures', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('picture_admin_breakpoints'), + 'access arguments' => array('administer pictures'), + 'file' => 'picture.admin.inc', + ); + + $items['admin/config/media/picture/groups'] = array( + 'title' => 'Groups', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 10, + ); + + $items['admin/config/media/picture/groups/global'] = array( + 'title' => 'Map breakpoints and image styles', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -1, + ); + + $items['admin/config/media/picture/groups/import'] = array( + 'title' => 'Import mappings', + 'page arguments' => array('picture_admin_import_form'), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer responsive images and styles'), + 'file' => 'picture.admin.inc', + 'weight' => 999, + ); + + $breakpoint_groups = breakpoints_breakpoint_group_load_all(); + foreach ($breakpoint_groups as $breakpoint_group_name => $breakpoint_group) { + if (!empty($breakpoint_group->machine_name)) { + $items['admin/config/media/picture/groups/' . $breakpoint_group->machine_name] = array( + 'title' => $breakpoint_group->name, + 'page arguments' => array('picture_admin_breakpoints', $breakpoint_group->machine_name), + 'type' => MENU_LOCAL_TASK, + 'access arguments' => array('administer pictures'), + 'file' => 'picture.admin.inc', + 'weight' => 15, + ); + $items['admin/config/media/picture/groups/' . $breakpoint_group->machine_name . '/export'] = array( + 'title' => 'Export ' . check_plain($breakpoint_group->name) . ' mappings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('picture_admin_export_form', 'mappings.' . $breakpoint_group->machine_name), + 'type' => MENU_LOCAL_ACTION, + 'access arguments' => array('administer responsive images and styles', 'mappings.' . $breakpoint_group->machine_name), + 'access callback' => 'picture_mappings_export_access', + 'file' => 'picture.admin.inc', + 'weight' => 15, + ); + } + } + + return $items; +} + +/** + * Access callback. + */ +function picture_mappings_export_access($perm, $mapping_name) { + return picture_mapping_load($mapping_name) && user_access($perm); +} + +/** + * Load mappings. + */ +function picture_mapping_load($name = NULL) { + ctools_include('export'); + if ($name) { + $mappings = ctools_export_load_object('picture_mapping', 'names', array($name)); + $mapping = isset($mappings[$name]) ? $mappings[$name] : FALSE; + return $mapping; + } + return ctools_export_load_object('picture_mapping'); +} + +/** + * Load all mappings. + */ +function picture_mapping_load_all() { + ctools_include('export'); + return ctools_export_load_object('picture_mapping'); +} + +/** + * Save mappings. + */ +function picture_mapping_save(&$mapping) { + ctools_include('export'); + $update = isset($mapping->id) ? array('id') : array(); + return drupal_write_record('picture_mapping', $mapping, $update); +} + +/** + * Implements hook_library(). + */ +function picture_library() { + $libraries['matchmedia'] = array( + 'title' => t('Matchmedia'), + 'website' => 'https://github.com/attiks/picturefill-proposal', + 'version' => '0.1', + 'js' => array( + drupal_get_path('module', 'picture') . '/picturefill/matchmedia.js' => array('type' => 'file', 'weight' => -10, 'group' => JS_DEFAULT), + ), + ); + $libraries['picturefill'] = array( + 'title' => t('Picturefill'), + 'website' => 'https://github.com/attiks/picturefill-proposal', + 'version' => '0.1', + 'js' => array( + drupal_get_path('module', 'picture') . '/picturefill/picturefill.js' => array('type' => 'file', 'weight' => -10, 'group' => JS_DEFAULT), + ), + ); + $libraries['picture.ajax'] = array( + 'title' => t('Ajax support for picture'), + 'version' => VERSION, + 'js' => array( + drupal_get_path('module', 'picture') . '/picture.js' => array('type' => 'file', 'weight' => -10, 'group' => JS_DEFAULT), + ), + ); + return $libraries; +} + +/** + * Empty picture object. + */ +function picture_empty_object() { + return (object)picture_empty_array(); +} + +/** + * Empty picture array. + */ +function picture_empty_array() { + return array( + 'machine_name' => '', + 'breakpoint_group' => '', + 'mapping' => array(), + ); +} + +/** + * Implements hook_ctools_plugin_api(). + * + * Lets CTools know which plugin APIs are implemented by picture module. + */ +function picture_ctools_plugin_api($owner, $api) { + static $api_versions = array( + 'file_entity' => array( + 'file_default_displays' => 1, + ), + ); + if (isset($api_versions[$owner][$api])) { + return array('version' => $api_versions[$owner][$api]); + } +} + +/** + * Validate mappings. + */ +function picture_mapping_validate($mapping) { + if (!is_object($mapping)) { + return FALSE; + } + foreach (array('machine_name', 'breakpoint_group', 'mapping') as $property) { + if (!property_exists($mapping, $property)) { + return FALSE; + } + } + return TRUE; +} + +/** + * Implements hook_theme(). + */ +function picture_theme() { + return array( + 'picture' => array( + 'variables' => array( + 'style_name' => NULL, + 'path' => NULL, + 'width' => NULL, + 'height' => NULL, + 'alt' => '', + 'title' => NULL, + 'attributes' => array(), + 'breakpoints' => array(), + ), + ), + 'picture_formatter' => array( + 'variables' => array( + 'item' => NULL, + 'path' => NULL, + 'image_style' => NULL, + 'breakpoints' => array(), + ), + ), + 'picture_source' => array( + 'variables' => array( + 'src' => NULL, + 'dimension' => NULL, + 'media' => NULL, + ), + ), + ); +} + +/** + * Implements hook_field_formatter_info(). + */ +function picture_field_formatter_info() { + $formatters = array( + 'picture' => array( + 'label' => t('Picture'), + 'field types' => array('image'), + 'settings' => array('picture_group' => '', 'fallback_image_style' => '', 'image_link' => ''), + ), + ); + + return $formatters; +} + +/** + * Implements hook_field_formatter_settings_form(). + */ +function picture_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + + $element['picture_group'] = array( + '#title' => t('Picture group'), + '#type' => 'select', + '#default_value' => $settings['picture_group'], + '#required' => TRUE, + '#options' => picture_get_mapping_options(), + ); + + $image_styles = image_style_options(FALSE); + $element['fallback_image_style'] = array( + '#title' => t('Fallback image style'), + '#type' => 'select', + '#default_value' => $settings['fallback_image_style'], + '#empty_option' => t('Automatic'), + '#options' => $image_styles, + ); + + $link_types = array( + 'content' => t('Content'), + 'file' => t('File'), + ); + $element['image_link'] = array( + '#title' => t('Link image to'), + '#type' => 'select', + '#default_value' => $settings['image_link'], + '#empty_option' => t('Nothing'), + '#options' => $link_types, + ); + + return $element; +} + +function picture_get_mapping_options() { + $picture_mapping_options = array(); + $picture_mappings = picture_mapping_load_all(); + if ($picture_mappings && !empty($picture_mappings)) { + foreach ($picture_mappings as $machine_name => $picture_mapping) { + $breakpoint_group = breakpoints_breakpoint_group_load($picture_mapping->breakpoint_group); + if ($breakpoint_group) { + $picture_mapping_options[$machine_name] = $breakpoint_group->name; + } + } + } + return $picture_mapping_options; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function picture_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + + $summary = array(); + + $picture_mapping = picture_mapping_load($settings['picture_group']); + $breakpoint_group = breakpoints_breakpoint_group_load($picture_mapping->breakpoint_group); + if ($breakpoint_group) { + $summary[] = t('Picture group: @picture_group', array('@picture_group' => $breakpoint_group->name)); + } + else { + $summary[] = t("Picture group doesn't exists"); + } + + $image_styles = image_style_options(FALSE); + unset($image_styles['']); + if (isset($image_styles[$settings['fallback_image_style']])) { + $summary[] = t('Fallback Image style: @style', array('@style' => $image_styles[$settings['fallback_image_style']])); + } + else { + $summary[] = t('Automatic fallback'); + } + + $link_types = array( + 'content' => t('Linked to content'), + 'file' => t('Linked to file'), + ); + // Display this setting only if image is linked. + if (isset($link_types[$settings['image_link']])) { + $summary[] = $link_types[$settings['image_link']]; + } + + return implode('<br />', $summary); +} + +/** + * Implements hook_field_formatter_view(). + */ +function picture_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + + // Check if the formatter involves a link. + if ($display['settings']['image_link'] == 'content') { + $uri = entity_uri($entity_type, $entity); + } + elseif ($display['settings']['image_link'] == 'file') { + $link_file = TRUE; + } + + $breakpoint_styles = array(); + $fallback_image_style = ''; + $group_name = $display['settings']['picture_group']; + $mappings = picture_mapping_load($group_name); + if ($mappings) { + foreach ($mappings->mapping as $breakpoint_name => $multipliers) { + if (!empty($multipliers)) { + foreach ($multipliers as $multiplier => $image_style) { + if (!empty($image_style)) { + if (empty($fallback_image_style)) { + $fallback_image_style = $image_style; + } + if (!isset($breakpoint_styles[$breakpoint_name])) { + $breakpoint_styles[$breakpoint_name] = array(); + } + $breakpoint_styles[$breakpoint_name][$multiplier] = $image_style; + } + } + } + } + } + + if (isset($display['settings']['fallback_image_style']) && !empty($display['settings']['fallback_image_style'])) { + $fallback_image_style = $display['settings']['fallback_image_style']; + } + + foreach ($items as $delta => $item) { + if (isset($link_file)) { + $uri = array( + 'path' => file_create_url($item['uri']), + 'options' => array(), + ); + } + $element[$delta] = array( + '#theme' => 'picture_formatter', + '#attached' => array('library' => array( + array('picture', 'matchmedia'), + array('picture', 'picturefill'), + array('picture', 'picture.ajax'), + )), + '#item' => $item, + '#image_style' => $fallback_image_style, + '#breakpoints' => $breakpoint_styles, + '#path' => isset($uri) ? $uri : '', + ); + } + + return $element; +} + +function theme_picture_formatter($variables) { + if (!isset($variables['breakpoints']) || empty($variables['breakpoints'])) { + return theme('image_formatter', $variables); + } + + $item = $variables['item']; + + // Do not output an empty 'title' attribute. + if (isset($item['title']) && drupal_strlen($item['title']) == 0) { + unset($item['title']); + } + + $item['style_name'] = $variables['image_style']; + $item['breakpoints'] = $variables['breakpoints']; + + if (!isset($item['path']) && isset($variables['uri'])) { + $item['path'] = $variables['uri']; + } + $output = theme('picture', $item); + + if (isset($variables['path']['path'])) { + $path = $variables['path']['path']; + $options = isset($variables['path']['options']) ? $variables['path']['options'] : array(); + $options['html'] = TRUE; + $output = l($output, $path, $options); + } + return $output; +} + +/** + * Returns HTML for a picture. + * + * @param $variables + * An associative array containing: + * - uri: Either the path of the image file (relative to base_path()) or a + * full URL. + * - width: The width of the image (if known). + * - height: The height of the image (if known). + * - alt: The alternative text for text-based browsers. + * - breakpoints: An array containing breakpoints. + * + * @ingroup themeable + */ +function theme_picture($variables) { + // Make sure that width and height are proper values + // If they exists we'll output them + // @see http://www.w3.org/community/respimg/2012/06/18/florians-compromise/ + if (isset($variables['width']) && empty($variables['width'])) { + unset($variables['width']); + unset($variables['height']); + } + elseif (isset($variables['height']) && empty($variables['height'])) { + unset($variables['width']); + unset($variables['height']); + } + + $sources = array(); + $output = array(); + + // Fallback image, output as source with media query. + $sources[] = array( + 'src' => image_style_url($variables['style_name'], $variables['uri']), + 'dimensions' => picture_get_image_dimensions($variables), + ); + + // All breakpoints and multipliers. + foreach ($variables['breakpoints'] as $breakpoint_name => $multipliers) { + $breakpoint = breakpoints_breakpoint_load_by_fullkey($breakpoint_name); + if ($breakpoint) { + $new_sources = array(); + foreach ($multipliers as $multiplier => $image_style) { + $new_source = $variables; + $new_source['style_name'] = $image_style; + $new_source['#media_query'] = picture_get_multiplier_media_query($multiplier, $breakpoint->breakpoint); + $new_sources[] = $new_source; + } + + foreach ($new_sources as $new_source) { + $sources[] = array( + 'src' => image_style_url($new_source['style_name'], $new_source['uri']), + 'dimensions' => picture_get_image_dimensions($new_sources[0]), + 'media' => $new_source['#media_query'], + ); + } + } + } + + if (!empty($sources)) { + $attributes = array(); + foreach (array('alt', 'title') as $key) { + if (isset($variables[$key])) { + $attributes['data-' . $key] = $variables[$key]; + } + } + $output[] = '<span data-picture' . drupal_attributes($attributes) . '>'; + + // Add source tags to the output. + foreach ($sources as $source) { + $output[] = theme('picture_source', $source); + } + + // Output the fallback image. + if (empty($variables['path'])) { + $variables['path'] = $variables['uri']; + } + + $output[] = '<noscript>' . theme('image_style', $variables) . '</noscript>'; + $output[] = '</span>'; + return implode("\n", $output); + } +} + +/** + * Generates the media query for multipliers of an image + * + * @param $multiplier + * A string containing the multiplier for which the media query is for. + * + * @param $breakpoint + * A string containing the breakpoint media query. + * + * @return sting + * The sting containing the media query for the multiplier. + */ +function picture_get_multiplier_media_query($multiplier, $breakpoint) { + $media_query = $breakpoint; + if($multiplier != '1x') { + $multiplier_formatted = str_replace('x', '', $multiplier); + $media_query = $breakpoint . ' and (min-device-pixel-ratio: ' . $multiplier_formatted . '), '; + $media_query .= $breakpoint . ' and (-o-min-device-pixel-ratio: ' . $multiplier_formatted . '), '; + $media_query .= $breakpoint . ' and (-webkit-min-device-pixel-ratio: ' . $multiplier_formatted . '), '; + $media_query .= $breakpoint . ' and (min-resolution: ' . $multiplier_formatted . 'dppx)'; + } + return $media_query; +} + +/** + * Returns HTML for a source tag. + * + * @param type $variables + * An associative array containing: + * - media: The media query to use. + * - src: Either the path of the image file (relative to base_path()) or a + * full URL. + * - dimensions: The width and height of the image (if known). + * + * @ingroup themeable + */ +function theme_picture_source($variables) { + $output = array(); + if (isset($variables['media']) && !empty($variables['media'])) { + $output[] = '<span data-media="' . $variables['media'] . '" data-src="' . $variables['src'] . '" ' . drupal_attributes($variables['dimensions']) . '></span>'; + } + else { + $output[] = '<span data-src="' . $variables['src'] . '" ' . drupal_attributes($variables['dimensions']) . '></span>'; + } + return implode("\n", $output); +} + +/** + * Determines the dimensions of an image. + * + * @param $variables + * An associative array containing: + * - style_name: The name of the style to be used to alter the original image. + * - width: The width of the source image (if known). + * - height: The height of the source image (if known). + * + * @return array + * Dimensions to be modified - an array with components width and height, in + * pixels. + */ +function picture_get_image_dimensions($variables) { + // Determine the dimensions of the styled image. + $dimensions = array( + 'width' => $variables['width'], + 'height' => $variables['height'], + ); + + image_style_transform_dimensions($variables['style_name'], $dimensions); + + return $dimensions; +} + +/** + * Implements hook_file_formatter_info(). + */ +function picture_file_formatter_info() { + $formatters['file_picture'] = array( + 'label' => t('Picture'), + 'default settings' => array( + 'picture_group' => '', + 'fallback_image_style' => '', + 'alt' => '', + 'title' => '', + ), + 'view callback' => 'picture_file_formatter_picture_view', + 'settings callback' => 'picture_file_formatter_picture_settings', + ); + + return $formatters; +} + +/** + * 'view callback' for hook_file_formatter_info(). + */ +function picture_file_formatter_picture_view($file, $display, $langcode) { + // Prevent PHP notices when trying to read empty files. + // @see http://drupal.org/node/681042 + if (!$file->filesize) { + return; + } + + // Do not bother proceeding if this file does not have an image mime type. + if (strpos($file->filemime, 'image/') !== 0) { + return; + } + + if (file_entity_file_is_readable($file) && isset($file->image_dimensions)) { + $breakpoint_styles = array(); + $fallback_image_style = ''; + $group_name = $display['settings']['picture_group']; + $mappings = picture_mapping_load($group_name); + if ($mappings) { + foreach ($mappings->mapping as $breakpoint_name => $multipliers) { + if (!empty($multipliers)) { + foreach ($multipliers as $multiplier => $image_style) { + if (!empty($image_style)) { + if (empty($fallback_image_style)) { + $fallback_image_style = $image_style; + } + if (!isset($breakpoint_styles[$breakpoint_name])) { + $breakpoint_styles[$breakpoint_name] = array(); + } + $breakpoint_styles[$breakpoint_name][$multiplier] = $image_style; + } + } + } + } + } + + if (isset($display['settings']['fallback_image_style']) && !empty($display['settings']['fallback_image_style'])) { + $fallback_image_style = $display['settings']['fallback_image_style']; + } + + $replace_options = array( + 'clear' => 1, + 'sanitize' => 0, + ); + $element = array( + '#theme' => 'picture_formatter', + '#attached' => array('library' => array( + array('picture', 'matchmedia'), + array('picture', 'picturefill'), + array('picture', 'picture.ajax'), + )), + '#item' => array( + 'style_name' => $display['settings']['image_style'], + 'path' => $file->uri, + 'uri' => $file->uri, + 'width' => $file->image_dimensions['width'], + 'height' => $file->image_dimensions['height'], + 'alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options), + 'title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options), + ), + '#image_style' => $fallback_image_style, + '#breakpoints' => $breakpoint_styles, + '#path' => '', + ); + + return $element; + } +} + +/** + * 'settings callback' for hook_file_formatter_info(). + */ +function picture_file_formatter_picture_settings($form, &$form_state, $settings) { + $picture_group_options = array(); + $picture_mappings = picture_mapping_load_all(); + if ($picture_mappings && !empty($picture_mappings)) { + foreach ($picture_mappings as $machine_name => $picture_mapping) { + $breakpoint_group = breakpoints_breakpoint_group_load($picture_mapping->breakpoint_group); + if ($breakpoint_group) { + $picture_group_options[$machine_name] = $breakpoint_group->name; + } + } + } + + $element['picture_group'] = array( + '#title' => t('Picture group'), + '#type' => 'select', + '#default_value' => $settings['picture_group'], + '#required' => TRUE, + '#options' => $picture_group_options, + ); + + $image_styles = image_style_options(FALSE); + $element['fallback_image_style'] = array( + '#title' => t('Fallback image style'), + '#type' => 'select', + '#default_value' => $settings['fallback_image_style'], + '#empty_option' => t('Automatic'), + '#options' => $image_styles, + ); + + $element['alt'] = array( + '#title' => t('Alt attribute'), + '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'), + '#type' => 'textfield', + '#default_value' => $settings['alt'], + ); + + // Allow the setting of the title attribute. + $element['title'] = array( + '#title' => t('Title attribute'), + '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'), + '#type' => 'textfield', + '#default_value' => $settings['title'], + ); + + return $element; +} diff --git a/sites/all/modules/picture/picturefill/matchmedia.js b/sites/all/modules/picture/picturefill/matchmedia.js new file mode 100644 index 0000000000000000000000000000000000000000..2daa2cf45bd2cd8ce4d75809b12444d9cb5bf983 --- /dev/null +++ b/sites/all/modules/picture/picturefill/matchmedia.js @@ -0,0 +1,195 @@ +/** + * Polyfill the behavior of window.matchMedia. + * + * @see http://dev.w3.org/csswg/cssom-view/#widl-Window-matchMedia-MediaQueryList-DOMString-query + * + * Test whether a CSS media type or media query applies. Register listeners + * to MediaQueryList objects. + * + * Adapted from https://github.com/paulirish/matchMedia.js with the addition + * of addListener and removeListener. The polyfill referenced above uses + * polling to trigger registered listeners on matchMedia tests. + * This polyfill triggers tests on window resize and orientationchange. + */ + +window.matchMedia = window.matchMedia || (function (doc, window) { + + "use strict"; + + var docElem = doc.documentElement; + var refNode = docElem.firstElementChild || docElem.firstChild; + // fakeBody required for <FF4 when executed in <head>. + var fakeBody = doc.createElement("body"); + var div = doc.createElement("div"); + + div.id = "mq-test-1"; + div.style.cssText = "position:absolute;top:-100em"; + fakeBody.style.background = "none"; + fakeBody.appendChild(div); + + // Global cache to speed up media matching (in IE). + var _cache = {}; + + // Store current dimensions, so we can clear the cache when needed. + var _currentDimensions = { + width: window.innerWidth || doc.documentElement.clientWidth, + height: window.innerHeight || doc.documentElement.clientHeight + }; + + function resetCache() { + // Store the new dimensions. + _currentDimensions = { + width: window.innerWidth || doc.documentElement.clientWidth, + height: window.innerHeight || doc.documentElement.clientHeight + }; + // Clear cache. + _cache = {}; + } + + // Clear cache on resize and orientationchange events. + if ('addEventListener' in window) { + window.addEventListener('resize', resetCache); + window.addEventListener('orientationchange', resetCache); + } + else if ('attachEvent' in window) { + window.attachEvent('onresize', resetCache); + window.attachEvent('onorientationchange', resetCache); + } + + /** + * A replacement for the native MediaQueryList object. + * + * @param {String} q + * A media query e.g. "screen" or "screen and (min-width: 28em)". + */ + function MediaQueryList (q) { + this.media = q; + this.matches = false; + if (_cache.hasOwnProperty(q)) { + this.matches = _cache[q]; + } + else { + this.check.call(this); + _cache[q] = this.matches; + } + } + + /** + * Polyfill the addListener and removeListener methods. + */ + MediaQueryList.prototype = { + listeners: [], + + /** + * Perform the media query application check. + */ + check: function () { + var isApplied; + div.innerHTML = "­<style media=\"" + this.media + "\"> #mq-test-1 {width: 42px;}</style>"; + docElem.insertBefore(fakeBody, refNode); + isApplied = div.offsetWidth === 42; + docElem.removeChild(fakeBody); + this.matches = isApplied; + }, + + /** + * Polyfill the addListener method of the MediaQueryList object. + * + * @param {Function} callback + * The callback to be invoked when the media query is applicable. + * + * @return {Object MediaQueryList} + * A MediaQueryList object that indicates whether the registered media + * query applies. The matches property is true when the media query + * applies and false when not. The original media query is referenced in + * the media property. + */ + addListener: function (callback) { + var handler = (function (mql, debounced) { + return function () { + // Only execute the callback if the state has changed. + var oldstate = mql.matches; + mql.check(); + if (oldstate != mql.matches) { + debounced.call(mql, mql); + } + }; + }(this, debounce(callback, 250))); + this.listeners.push({ + 'callback': callback, + 'handler': handler + }); + + // Associate the handler to the resize and orientationchange events. + if ('addEventListener' in window) { + window.addEventListener('resize', handler); + window.addEventListener('orientationchange', handler); + } + else if ('attachEvent' in window) { + window.attachEvent('onresize', handler); + window.attachEvent('onorientationchange', handler); + } + }, + + /** + * Polyfill the removeListener method of the MediaQueryList object. + * + * @param {Function} callback + * The callback to be removed from the set of listeners. + */ + removeListener: function (callback) { + for (var i = 0, listeners = this.listeners; i < listeners.length; i++) { + if (listeners[i].callback === callback) { + // Disassociate the handler to the resize and orientationchange events. + if ('removeEventListener' in window) { + window.removeEventListener('resize', listeners[i].handler); + window.removeEventListener('orientationchange', listeners[i].handler); + } + else if ('detachEvent' in window) { + window.detachEvent('onresize', listeners[i].handler); + window.detachEvent('onorientationchange', listeners[i].handler); + } + listeners.splice(i, 1); + } + } + } + }; + + /** + * Limits the invocations of a function in a given time frame. + * + * @param {Function} callback + * The function to be invoked. + * + * @param {Number} wait + * The time period within which the callback function should only be + * invoked once. For example if the wait period is 250ms, then the callback + * will only be called at most 4 times per second. + */ + function debounce (callback, wait) { + var timeout, result; + return function () { + var context = this; + var args = arguments; + var later = function () { + timeout = null; + result = callback.apply(context, args); + }; + window.clearTimeout(timeout); + timeout = window.setTimeout(later, wait); + return result; + }; + } + + /** + * Return a MediaQueryList. + * + * @param {String} q + * A media query e.g. "screen" or "screen and (min-width: 28em)". The media + * query is checked for applicability before the object is returned. + */ + return function (q) { + // Build a new MediaQueryList object with the result of the check. + return new MediaQueryList(q); + }; +}(document, window)); diff --git a/sites/all/modules/picture/picturefill/picturefill.js b/sites/all/modules/picture/picturefill/picturefill.js new file mode 100644 index 0000000000000000000000000000000000000000..a03ea2e492686e4376ee264023cc936b73a3465c --- /dev/null +++ b/sites/all/modules/picture/picturefill/picturefill.js @@ -0,0 +1,78 @@ +/*jshint loopfunc: true, browser: true, curly: true, eqeqeq: true, expr: true, forin: true, latedef: true, newcap: true, noarg: true, trailing: true, undef: true, unused: true */ +/*! Picturefill - Author: Scott Jehl, 2012 | License: MIT/GPLv2 */ +(function(w, parent){ + + // Enable strict mode. + "use strict"; + + w.picturefill = function(parent) { + // Copy attributes from the source to the destination. + function _copyAttributes(src, tar) { + if (src.getAttribute('width') && src.getAttribute('height')) { + tar.width = src.getAttribute('width'); + tar.height = src.getAttribute('height'); + } + } + + // Get all picture tags. + if (!parent || !parent.getElementsByTagName) { + parent = w.document; + } + var ps = parent.getElementsByTagName('span'); + + // Loop the pictures. + for (var i = 0, il = ps.length; i < il; i++ ) { + if (ps[i].getAttribute('data-picture') !== null) { + var sources = ps[i].getElementsByTagName('span'); + var picImg = null; + var matches = []; + + // See which sources match. + for (var j = 0, jl = sources.length; j < jl; j++ ) { + var media = sources[j].getAttribute('data-media'); + + // If there's no media specified or the media query matches, add it. + if (!media || (w.matchMedia && w.matchMedia(media).matches)) { + matches.push(sources[j]); + } + } + + if (matches.length) { + // Grab the most appropriate (last) match. + var match = matches.pop(); + + // Find any existing img element in the picture element. + picImg = ps[i].getElementsByTagName('img')[0]; + + // Add a new img element if one doesn't exists. + if (!picImg) { + picImg = w.document.createElement('img'); + picImg.alt = ps[i].getAttribute('data-alt'); + picImg.title = ps[i].getAttribute('data-title'); + ps[i].appendChild(picImg); + } + + // Set the source if it's different. + if (picImg.getAttribute('src') !== match.getAttribute('data-src')) { + picImg.src = match.getAttribute('data-src'); + _copyAttributes(match, picImg); + } + } + } + } + }; + + // Run on resize and domready (w.load as a fallback) + if (w.addEventListener) { + w.addEventListener('resize', w.picturefill, false); + w.addEventListener('DOMContentLoaded', function() { + w.picturefill(); + // Run once only. + w.removeEventListener('load', w.picturefill, false); + }, false); + w.addEventListener('load', w.picturefill, false); + } + else if (w.attachEvent) { + w.attachEvent('onload', w.picturefill); + } +})(this); \ No newline at end of file diff --git a/sites/all/modules/robotstxt/LICENSE.txt b/sites/all/modules/robotstxt/LICENSE.txt deleted file mode 100644 index 2c095c8d3f42488e8168f9710a4ffbfc4125a159..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/LICENSE.txt +++ /dev/null @@ -1,274 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - - Version 2, June 1991 - -Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, -Cambridge, MA 02139, 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 Library 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 diff --git a/sites/all/modules/robotstxt/README.txt b/sites/all/modules/robotstxt/README.txt deleted file mode 100644 index 5774e208d76c7d7935f07528cac885e4c5e60ce6..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/README.txt +++ /dev/null @@ -1,65 +0,0 @@ -$Id: README.txt,v 1.3.2.2 2011/01/05 23:24:10 hass Exp $ - -CONTENTS OF THIS FILE ---------------------- - - * Introduction - * Installation - * Frequently Asked Questions (FAQ) - * Known Issues - * How Can You Contribute? - - -INTRODUCTION ------------- - -Maintainer: hass <http://drupal.org/user/85918> -Project Page: http://drupal.org/project/robotstxt - -Use this module when you are running multiple Drupal sites from a single code -base (multisite) and you need a different robots.txt file for each one. This -module generates the robots.txt file dynamically and gives you the chance to -edit it, on a per-site basis. - -For developers, you can automatically add paths to the robots.txt file by -implementing hook_robotstxt(). See robotstxt.api.php for more documentation. - - -INSTALLATION ------------- - -See http://drupal.org/getting-started/install-contrib for instructions on -how to install or update Drupal modules. - -Once you have the RobotsTxt modules installed, make sure to delete or rename -the robots.txt file in the root of your Drupal installation. Otherwise, the -module cannot intercept requests for the /robots.txt path. - - -FREQUENTLY ASKED QUESTIONS --------------------------- - -Q: Can this module work if I have clean URLs disabled? -A: Yes it can! In the .htaccess file of your Drupal's root directory, add the - following two lines to the mod_rewrite section, immediately after the line - that says "RewriteEngine on": - - # Add redirection for the robots.txt path for use with the RobotsTxt module. - RewriteRule ^(robots.txt)$ index.php?q=$1 - - -KNOWN ISSUES ------------- - -There are no known issues at this time. - -To report new bug reports, feature requests, and support requests, visit -http://drupal.org/project/issues/robotstxt. - - -HOW CAN YOU CONTRIBUTE? ---------------------- - -- Report any bugs, feature requests, etc. in the issue tracker. - http://drupal.org/project/issues/robotstxt - diff --git a/sites/all/modules/robotstxt/robotstxt.admin.inc b/sites/all/modules/robotstxt/robotstxt.admin.inc deleted file mode 100644 index bde618f5b0e1a1c05e5d6c5155bf4731aae27ee6..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.admin.inc +++ /dev/null @@ -1,25 +0,0 @@ -<?php -// $Id: robotstxt.admin.inc,v 1.3.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * @file - * Administrative page callbacks for the robotstxt module. - */ - -/** - * Administration settings form. - * - * @see system_settings_form() - */ -function robotstxt_admin_settings() { - $form['robotstxt'] = array( - '#type' => 'textarea', - '#title' => t('Contents of robots.txt'), - '#default_value' => _robotstxt_get_content(), - '#cols' => 60, - '#rows' => 20, - '#wysiwyg' => FALSE, - ); - - return system_settings_form($form, FALSE); -} diff --git a/sites/all/modules/robotstxt/robotstxt.api.php b/sites/all/modules/robotstxt/robotstxt.api.php deleted file mode 100644 index b481161bbce6ad325c300f0c1dfb942cd144aeba..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.api.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -// $Id: robotstxt.api.php,v 1.2.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * @file - * Hooks provided by the robotstxt module. - */ - -/** - * Add additional lines to the site's robots.txt file. - * - * @return - * An array of strings to add to the robots.txt. - */ -function hook_robotstxt() { - return array( - 'Disallow: /foo', - 'Disallow: /bar', - ); -} diff --git a/sites/all/modules/robotstxt/robotstxt.info b/sites/all/modules/robotstxt/robotstxt.info deleted file mode 100644 index 7c937b7cb3146e41d5732116bc3816ba3fc8536c..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.info +++ /dev/null @@ -1,15 +0,0 @@ -; $Id: robotstxt.info,v 1.6.2.2 2011/01/05 23:24:10 hass Exp $ -name = robots.txt -description = "Generates the robots.txt file dynamically and gives you the chance to edit it, on a per-site basis, from the web UI." -core = 7.x -files[] = robotstxt.module -files[] = robotstxt.admin.inc -files[] = robotstxt.install -configure = admin/config/search/robotstxt - -; Information added by drupal.org packaging script on 2011-01-05 -version = "7.x-1.0" -core = "7.x" -project = "robotstxt" -datestamp = "1294270341" - diff --git a/sites/all/modules/robotstxt/robotstxt.install b/sites/all/modules/robotstxt/robotstxt.install deleted file mode 100644 index c5d70fb79586ffd5af4f98e1f384b6a2ab9ad313..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.install +++ /dev/null @@ -1,85 +0,0 @@ -<?php -// $Id: robotstxt.install,v 1.13.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * Implements hook_install(). - */ -function robotstxt_install() { - if (file_exists(DRUPAL_ROOT . '/robots.txt')) { - variable_set('robotstxt', file_get_contents(DRUPAL_ROOT . '/robots.txt')); - } - elseif (file_exists(drupal_get_path('module', 'robotstxt') . '/robots.txt')) { - variable_set('robotstxt', file_get_contents(drupal_get_path('module', 'robotstxt') . '/robots.txt')); - } -} - -/** - * Implements hook_uninstall(). - */ -function robotstxt_uninstall() { - variable_del('robotstxt'); -} - -/** - * Implements hook_requirements(). - */ -function robotstxt_requirements($phase) { - $requirements = array(); - $t = get_t(); - - switch ($phase) { - case 'runtime' : - // Module cannot work without Clean URLs. - if (!variable_get('clean_url', 0)) { - $requirements['robotstxt_cleanurl'] = array( - 'title' => $t('RobotsTxt'), - 'severity' => REQUIREMENT_ERROR, - 'value' => $t('<a href="!clean_url">Clean URLs</a> are mandatory for this module.', array('!clean_url' => url('admin/config/search/clean-urls'))), - ); - } - - // Webservers prefer the robots.txt file on disk and does not allow menu path overwrite. - if (file_exists(DRUPAL_ROOT . '/robots.txt')) { - $requirements['robotstxt_file'] = array( - 'title' => $t('RobotsTxt'), - 'severity' => REQUIREMENT_WARNING, - 'value' => $t('RobotsTxt module works only if you remove the existing robots.txt file in your website root.'), - ); - } - } - return $requirements; -} - -/** - * Automatically add the 'administer robots.txt' permission to granted users. - */ -function robotstxt_update_6100() { - $roles = user_roles(FALSE, 'administer site configuration'); - foreach ($roles as $rid => $role) { - _update_7000_user_role_grant_permissions($rid, array('administer robots.txt'), 'robotstxt'); - } - return t("Added 'administer robots.txt' permission to all roles with 'administer site configuration' permission."); -} - -/** - * #337820: Rename menu path 'logout' to 'user/logout' for consistency. - */ -function robotstxt_update_7000() { - $robotstxt = variable_get('robotstxt'); - $robotstxt = str_replace('Disallow: /logout/', 'Disallow: /user/logout/', $robotstxt); - $robotstxt = str_replace('Disallow: /?q=logout/', 'Disallow: /?q=user/logout/', $robotstxt); - variable_set('robotstxt', $robotstxt); - - return t("Renamed menu path 'logout' to 'user/logout'."); -} - -/** - * #494462: Allow crawling of sites/default/files by search engines, don't disallow it in robots.txt. - */ -function robotstxt_update_7100() { - $robotstxt = variable_get('robotstxt'); - $robotstxt = preg_replace("/Disallow:\s\/sites\/(\r\n?|\n)/", '', $robotstxt); - variable_set('robotstxt', $robotstxt); - - return t("Allowed crawling of sites/default/files by search engines."); -} diff --git a/sites/all/modules/robotstxt/robotstxt.module b/sites/all/modules/robotstxt/robotstxt.module deleted file mode 100755 index 47e2354d83f4eb4fc058942716c18e88519614f6..0000000000000000000000000000000000000000 --- a/sites/all/modules/robotstxt/robotstxt.module +++ /dev/null @@ -1,97 +0,0 @@ -<?php -// $Id: robotstxt.module,v 1.9.2.2 2011/01/05 23:24:10 hass Exp $ - -/** - * Implements hook_help(). - */ -function robotstxt_help($path, $arg) { - switch ($path) { - case 'admin/help#robotstxt': - return '<p>'. t('In a multisite environment, there is no mechanism for having a separate robots.txt file for each site. This module addresses that need by letting you administer the robots.txt file from the settings interface.') .'</p>'; - break; - - case 'admin/config/search/robotstxt': - if (file_exists('./robots.txt')) { - drupal_set_message(t('One or more problems have been detected with the RobotsTxt configuration. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'warning'); - } - return t('See <a href="http://www.robotstxt.org/">http://www.robotstxt.org/</a> for more information concerning how to write your <a href="@robotstxt">robots.txt</a> file.', array('@robotstxt' => base_path() . 'robots.txt')); - break; - } -} - -/** - * Implements hook_permission(). - */ -function robotstxt_permission() { - return array( - 'administer robots.txt' => array( - 'title' => t('Administer robots.txt'), - 'description' => t('Perform maintenance tasks for robots.txt.'), - ), - ); -} - -/** - * Implements hook_menu(). - */ -function robotstxt_menu() { - $items['robots.txt'] = array( - 'page callback' => 'robotstxt_robots', - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, - ); - $items['admin/config/search/robotstxt'] = array( - 'title' => 'RobotsTxt', - 'description' => 'Manage your robots.txt file.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('robotstxt_admin_settings'), - 'access arguments' => array('administer robots.txt'), - 'file' => 'robotstxt.admin.inc', - ); - - return $items; -} - -/** - * Show the robots.txt file. - */ -function robotstxt_robots() { - $content = array(); - $content[] = _robotstxt_get_content(); - - // Hook other modules for adding additional lines. - if ($additions = module_invoke_all('robotstxt')) { - $content = array_merge($content, $additions); - } - - // Trim any extra whitespace and filter out empty strings. - $content = array_map('trim', $content); - $content = array_filter($content); - - drupal_add_http_header('Content-type', 'text/plain'); - echo implode("\n", $content); - exit; -} - -/** - * Retrieve contents of robots.txt from the database variable, site root, or - * module directory. - */ -function _robotstxt_get_content() { - $content = variable_get('robotstxt', FALSE); - - if ($content === FALSE) { - $files = array( - DRUPAL_ROOT . '/robots.txt', - drupal_get_path('module', 'robotstxt') . '/robots.txt', - ); - foreach ($files as $file) { - if (file_exists($file) && is_readable($file)) { - $content = file_get_contents($file); - break; - } - } - } - - return $content; -} diff --git a/sites/all/modules/scanner/scanner.module b/sites/all/modules/scanner/scanner.module index 43c39813bb2c6eb967f993436e97035b7bf8090a..710acf40578ba673f9068f86e4ac65fed4aec1da 100644 --- a/sites/all/modules/scanner/scanner.module +++ b/sites/all/modules/scanner/scanner.module @@ -1101,7 +1101,7 @@ function scanner_admin_form($node, &$form_state) { $form['tables'][$key] = array( '#type' => 'checkbox', '#title' => filter_xss('<strong>' . $item['type'] . ':</strong> ' . $item['field'], $allowed_values = array('strong', 'p')), - '#default_value' => variable_get($key, FALSE), + '#default_value' => variable_get($key, TRUE), ); } @@ -1195,7 +1195,7 @@ function _scanner_get_selected_tables_map() { $tables_map = _scanner_get_all_tables_map(); foreach ($tables_map as $i => $item) { $key = 'scanner_' . $item['field'] . '_' . $item['table'] . '_' . $item['type']; - if (!variable_get($key, FALSE)) { + if (!variable_get($key, TRUE)) { unset($tables_map[$i]); } } diff --git a/sites/all/modules/tims b/sites/all/modules/tims new file mode 160000 index 0000000000000000000000000000000000000000..f565f31a3856d147bfb8bfd56ebccb14826a1228 --- /dev/null +++ b/sites/all/modules/tims @@ -0,0 +1 @@ +Subproject commit f565f31a3856d147bfb8bfd56ebccb14826a1228 diff --git a/sites/all/modules/token/tests/token_test.info b/sites/all/modules/token/tests/token_test.info index a344aa29bd899a1049cb917cd3805f1dea474581..141ed5cfcb339ccb1d25dc3fc7156229e8687eaa 100644 --- a/sites/all/modules/token/tests/token_test.info +++ b/sites/all/modules/token/tests/token_test.info @@ -5,9 +5,9 @@ core = 7.x files[] = token_test.module hidden = TRUE -; Information added by drupal.org packaging script on 2012-09-24 -version = "7.x-1.4" +; Information added by drupal.org packaging script on 2013-02-24 +version = "7.x-1.5" core = "7.x" project = "token" -datestamp = "1348497279" +datestamp = "1361665026" diff --git a/sites/all/modules/token/token.drush.inc b/sites/all/modules/token/token.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..fc3235730d216f335682feef6749fbe1ef47d0de --- /dev/null +++ b/sites/all/modules/token/token.drush.inc @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Drush integration for the Token module. + */ + +/** + * Implements hook_drush_cache_clear(). + */ +function token_drush_cache_clear(&$types) { + if (function_exists('module_exists') && module_exists('token')) { + $types['token'] = 'drush_token_cache_clear_token_info'; + } +} + +/** + * Clear caches internal to Token module. + */ +function drush_token_cache_clear_token_info() { + token_clear_cache(); +} diff --git a/sites/all/modules/token/token.info b/sites/all/modules/token/token.info index 8d14814aca2c3bd773606c2d749be8d076bd8a2f..43fced193354ae6462affbd5fe79215aec4eee65 100644 --- a/sites/all/modules/token/token.info +++ b/sites/all/modules/token/token.info @@ -1,15 +1,11 @@ name = Token description = Provides a user interface for the Token API and some missing core tokens. core = 7.x -files[] = token.module -files[] = token.install -files[] = token.tokens.inc -files[] = token.pages.inc files[] = token.test -; Information added by drupal.org packaging script on 2012-09-24 -version = "7.x-1.4" +; Information added by drupal.org packaging script on 2013-02-24 +version = "7.x-1.5" core = "7.x" project = "token" -datestamp = "1348497279" +datestamp = "1361665026" diff --git a/sites/all/modules/token/token.js b/sites/all/modules/token/token.js index 88a75c1017e2e29cc8c0da42d73a989e43890f29..98d1ac3a5af68e585cdef59d75ed56bba34a694f 100644 --- a/sites/all/modules/token/token.js +++ b/sites/all/modules/token/token.js @@ -14,6 +14,12 @@ Drupal.behaviors.tokenDialog = { $('a.token-dialog', context).once('token-dialog').click(function() { var url = $(this).attr('href'); var dialog = $('<div style="display: none" class="loading">' + Drupal.t('Loading token browser...') + '</div>').appendTo('body'); + + // Emulate the AJAX data sent normally so that we get the same theme. + var data = {}; + data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme; + data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token; + dialog.dialog({ title: $(this).attr('title') || Drupal.t('Available tokens'), width: 700, @@ -24,7 +30,7 @@ Drupal.behaviors.tokenDialog = { // Load the token tree using AJAX. dialog.load( url, - {}, + data, function (responseText, textStatus, XMLHttpRequest) { dialog.removeClass('loading'); } diff --git a/sites/all/modules/token/token.module b/sites/all/modules/token/token.module index 7bba8a7427b71731961bbf3dd1a151af76781698..88bcc6093a476deda374beb4753021129b8d3acb 100644 --- a/sites/all/modules/token/token.module +++ b/sites/all/modules/token/token.module @@ -78,6 +78,7 @@ function token_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, 'file' => 'token.pages.inc', + 'theme callback' => 'ajax_base_page_theme', ); // Devel token pages. @@ -265,7 +266,7 @@ function token_form_block_admin_configure_alter(&$form, $form_state) { $form['settings']['title']['#description'] .= ' ' . t('This field supports tokens.'); // @todo Figure out why this token validation does not seem to be working here. $form['settings']['title']['#element_validate'][] = 'token_element_validate'; - $form['settings']['title']['#token_types'] = array(); + $form['settings']['title'] += array('#token_types' => array()); } /** @@ -377,23 +378,26 @@ function token_clear_cache() { * @see token_entity_info_alter() * @see http://drupal.org/node/737726 */ -function token_get_entity_mapping($value_type = 'token', $value = NULL) { +function token_get_entity_mapping($value_type = 'token', $value = NULL, $fallback = FALSE) { $mapping = &drupal_static(__FUNCTION__, array()); if (empty($mapping)) { foreach (entity_get_info() as $entity_type => $info) { $mapping[$entity_type] = !empty($info['token type']) ? $info['token type'] : $entity_type; } + // Allow modules to alter the mapping array. + drupal_alter('token_entity_mapping', $mapping); } if (!isset($value)) { - return $mapping; + return $value_type == 'token' ? array_flip($mapping) : $mapping; } elseif ($value_type == 'token') { - return array_search($value, $mapping); + $return = array_search($value, $mapping); + return $return !== FALSE ? $return : ($fallback ? $value : FALSE); } elseif ($value_type == 'entity') { - return isset($mapping[$value]) ? $mapping[$value] : FALSE; + return isset($mapping[$value]) ? $mapping[$value] : ($fallback ? $value : FALSE); } } @@ -739,24 +743,28 @@ function token_element_validate_token_context(&$element, &$form_state) { * Implements hook_form_FORM_ID_alter(). */ function token_form_field_ui_field_edit_form_alter(&$form, $form_state) { - if (!isset($form['instance'])) { + if (!isset($form['instance']) || !empty($form['#field']['locked'])) { return; } if (($form['#field']['type'] == 'file' || $form['#field']['type'] == 'image') && isset($form['instance']['settings']['file_directory']) && !module_exists('filefield_paths')) { // GAH! We can only support global tokens in the upload file directory path. $form['instance']['settings']['file_directory']['#element_validate'][] = 'token_element_validate'; - $form['instance']['settings']['file_directory']['#token_types'] = array(); - $form['instance']['settings']['token_tree'] = array( - '#theme' => 'token_tree', - '#token_types' => array(), - '#weight' => $form['instance']['settings']['file_directory']['#weight'] + 0.5, - ); + $form['instance']['settings']['file_directory'] += array('#token_types' => array()); $form['instance']['settings']['file_directory']['#description'] .= ' ' . t('This field supports tokens.'); } // Note that the description is tokenized via token_field_widget_form_alter(). $form['instance']['description']['#description'] .= '<br />' . t('This field supports tokens.'); + $form['instance']['description']['#element_validate'][] = 'token_element_validate'; + $form['instance']['description'] += array('#token_types' => array()); + + $form['instance']['settings']['token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array(), + '#dialog' => TRUE, + '#weight' => $form['instance']['description']['#weight'] + 0.5, + ); } /** @@ -775,6 +783,7 @@ function token_form_system_actions_configure_alter(&$form, $form_state) { $form['token_tree'] = array( '#theme' => 'token_tree', '#token_types' => 'all', + '#dialog' => TRUE, '#weight' => 100, ); // @todo Add token validation to the action fields that can use tokens. @@ -836,10 +845,11 @@ function token_form_user_admin_settings_alter(&$form, &$form_state) { } // Add the token tree UI. - $form['token_tree'] = array( + $form['email']['token_tree'] = array( '#theme' => 'token_tree', '#token_types' => array('user'), '#show_restricted' => TRUE, + '#dialog' => TRUE, '#weight' => 90, ); } diff --git a/sites/all/modules/token/token.pages.inc b/sites/all/modules/token/token.pages.inc index 905943ca074a3392a8e4a5c1de9d5f79da80d821..2341a9f981c6dc6c1ff88dff2d626a4bef2a734f 100644 --- a/sites/all/modules/token/token.pages.inc +++ b/sites/all/modules/token/token.pages.inc @@ -57,7 +57,7 @@ function token_page_output_tree() { $options['dialog'] = FALSE; $output = theme('token_tree', $options); - print '<html><head><title></title>' . drupal_get_css() . drupal_get_js() . '</head>'; + print '<html><head>' . drupal_get_css() . drupal_get_js() . '</head>'; print '<body class="token-tree">' . $output . '</body></html>'; drupal_exit(); } @@ -231,7 +231,7 @@ function _token_clean_css_identifier($id) { /** * Menu callback; prints the available tokens and values for an object. */ -function token_devel_token_object($entity_type, $entity) { +function token_devel_token_object($entity_type, $entity, $token_type = NULL) { $header = array( t('Token'), t('Value'), @@ -243,7 +243,10 @@ function token_devel_token_object($entity_type, $entity) { 'values' => TRUE, 'data' => array($entity_type => $entity), ); - $tree = token_build_tree($entity_type, $options); + if (!isset($token_type)) { + $token_type = $entity_type; + } + $tree = token_build_tree($token_type, $options); foreach ($tree as $token => $token_info) { if (!empty($token_info['restricted'])) { continue; diff --git a/sites/all/modules/token/token.test b/sites/all/modules/token/token.test index b7e9582425595cee521d51ff5593161fd542817e..59fa6457b5e937186a520d7e26833675c9061def 100644 --- a/sites/all/modules/token/token.test +++ b/sites/all/modules/token/token.test @@ -676,10 +676,12 @@ class TokenEntityTestCase extends TokenTestHelper { $this->assertIdentical(token_get_entity_mapping('token', 'term'), 'taxonomy_term'); $this->assertIdentical(token_get_entity_mapping('token', 'vocabulary'), 'taxonomy_vocabulary'); $this->assertIdentical(token_get_entity_mapping('token', 'invalid'), FALSE); + $this->assertIdentical(token_get_entity_mapping('token', 'invalid', TRUE), 'invalid'); $this->assertIdentical(token_get_entity_mapping('entity', 'node'), 'node'); $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_term'), 'term'); $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_vocabulary'), 'vocabulary'); $this->assertIdentical(token_get_entity_mapping('entity', 'invalid'), FALSE); + $this->assertIdentical(token_get_entity_mapping('entity', 'invalid', TRUE), 'invalid'); // Test that when we send the mis-matched entity type into token_replace() // that we still get the tokens replaced. diff --git a/sites/all/modules/token/token.tokens.inc b/sites/all/modules/token/token.tokens.inc index 3dc2d3b4f210a70f5c9da7507bad25f11f743042..e0c0b5e91305c98e549b199573779bd777cbf8c5 100644 --- a/sites/all/modules/token/token.tokens.inc +++ b/sites/all/modules/token/token.tokens.inc @@ -80,7 +80,7 @@ function token_token_info_alter(&$info) { foreach ($date_format_types as $date_format_type => $date_format_type_info) { if (!isset($info['tokens']['date'][$date_format_type])) { $info['tokens']['date'][$date_format_type] = array( - 'name' => $date_format_type_info['title'], + 'name' => check_plain($date_format_type_info['title']), 'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))), 'module' => 'token', ); diff --git a/sites/all/modules/unl/includes/common.php b/sites/all/modules/unl/includes/common.php index 22dc7cc8702fda4c0ddbdf23e7e9738f2febff59..a00e4fb588807c7fa4928045028a8a36e90c6949 100644 --- a/sites/all/modules/unl/includes/common.php +++ b/sites/all/modules/unl/includes/common.php @@ -204,7 +204,7 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) { unl_load_zend_framework(); if (!Zend_Uri::check($url)) { - drupal_set_message('A non-url was passed to ' . __FUNCTION__ . '().', 'warning'); + watchdog('unl', 'A non-url was passed to %func().', array('%func' => __FUNCTION__), WATCHDOG_WARNING); return FALSE; } @@ -217,16 +217,29 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) // If cached in the static array, return it. if (array_key_exists($url, $static)) { $headers = $static[$url]['headers']; + + // Don't let this page be cached since it contains uncacheable content. + $GLOBALS['conf']['cache'] = FALSE; + return $static[$url]['body']; } - // If cached in the drupla cache, return it. + // If cached in the drupal cache, return it. $data = cache_get(__FUNCTION__ . $url); if ($data && time() < $data->data['expires']) { $headers = $data->data['headers']; + + // Don't let this page be cached any longer than the retrieved content. + $GLOBALS['conf']['page_cache_maximum_age'] = min(variable_get('page_cache_maximum_age', 0), $data->data['expires'] - time()); + return $data->data['body']; } + if (!$context) { + // Set a 5 second timeout + $context = stream_context_create(array('http' => array('timeout' => 5))); + } + // Make the request $http_response_header = array(); $body = file_get_contents($url, NULL, $context); @@ -256,7 +269,13 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) $matches = array(); if (preg_match('/max-age=([0-9]+)/', $cacheControl, $matches)) { $expires = time() + $matches[1]; - $cacheable = TRUE; + if (array_key_exists('age', $lowercaseHeaders)) { + $expires -= $lowercaseHeaders['age']; + } + + if ($expires > time()) { + $cacheable = TRUE; + } } if (strpos($cacheControl, 'private') !== FALSE) { $cacheable = FALSE; @@ -279,6 +298,9 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) 'expires' => $expires, ); cache_set(__FUNCTION__ . $url, $data, 'cache', $expires); + + // Don't let this page be cached any longer than the retrieved content. + $GLOBALS['conf']['page_cache_maximum_age'] = min(variable_get('page_cache_maximum_age', 0), $expires - time()); } // Otherwise just save to the static per-request cache else { @@ -286,6 +308,9 @@ function unl_url_get_contents($url, $context = NULL, &$headers = array()) 'body' => $body, 'headers' => $headers, ); + + // Don't let this page be cached since it contains uncacheable content. + $GLOBALS['conf']['cache'] = FALSE; } return $body; diff --git a/sites/all/modules/unl/unl.module b/sites/all/modules/unl/unl.module index 63dd41d1b0dfb304836a929a22618146787184bb..5a9b6f2c2a28bcdf3319ad5041bfe4dc8dda1f4f 100644 --- a/sites/all/modules/unl/unl.module +++ b/sites/all/modules/unl/unl.module @@ -581,6 +581,7 @@ function unl_form_alter(&$form, $form_state, $form_id) { '#type' => 'checkbox', '#title' => 'SSL Enabled', '#default_value' => variable_get('https', FALSE), + '#disabled' => !unl_user_is_administrator(), ); if (conf_path() != 'sites/default') { @@ -594,6 +595,7 @@ function unl_form_alter(&$form, $form_state, $form_id) { '#type' => 'select', '#options' => $base_urls, '#default_value' => variable_get('unl_primary_base_url'), + '#disabled' => !unl_user_is_administrator(), ); $form['#submit'][] = 'unl_system_settings_form_submit'; } @@ -1033,7 +1035,10 @@ function unl_query_alter(QueryAlterableInterface $query) { // Join it with the users_roles tables so that only users with roles are seleceted. $query->join('users_roles', 'unl_distinct_prefix_r', $usersTableAlias . '.uid = unl_distinct_prefix_r.uid'); if (!unl_user_is_administrator()) { + // Hide users that are only in the administrator role $query->where('unl_distinct_prefix_r.rid != ' . unl_shared_variable_get('user_admin_role', -1)); + // Hide the default admin user. + $query->where($usersTableAlias . '.uid != 1'); } } } @@ -1111,13 +1116,19 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac // Break down the URL target then rebuild it as absolute. $url = substr($match, 1, -1); + $url = html_entity_decode($url); $parts = parse_url($url); if (!isset($parts['scheme'])) { - $parts['scheme'] = $_SERVER['HTTPS'] ? 'https' : 'http'; + $parts['scheme'] = isset($_SERVER['HTTPS']) ? 'https' : 'http'; } if (!isset($parts['host'])) { - $parts['host'] = $_SERVER['HTTP_HOST']; + $parts['host'] = $_SERVER['SERVER_NAME']; } + /* We can't do this on production because we're running on port 8080. + if (!isset($parts['port']) && !in_array($_SERVER['SERVER_PORT'], array(80, 443))) { + $parts['port'] = $_SERVER['SERVER_PORT']; + } + */ if (isset($parts['path']) && substr($parts['path'], 0, 1) != '/') { if (variable_get('unl_use_base_tag')) { $parts['path'] = $GLOBALS['base_path'] . $parts['path']; @@ -1128,8 +1139,10 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac if (!isset($parts['path'])) { $parts['path'] = '/'; } - $url = $parts['scheme'] . '://' . $parts['host'] . $parts['path']; - + $url = $parts['scheme'] + . '://' . $parts['host'] + . (isset($parts['port']) ? ':' . $parts['port'] : '') + . $parts['path']; // If this is a request to another UNL site, add format=partial to the query. if (substr($parts['host'], -7) == 'unl.edu') { @@ -1149,31 +1162,19 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac $url .= '#' . $parts['fragment']; } - $ssiDepth = 0; - if (array_key_exists('HTTP_X_UNL_SSI_DEPTH', $_SERVER)) { - $ssiDepth = $_SERVER['HTTP_X_UNL_SSI_DEPTH']; - } - $ssiDepth++; - - $context = stream_context_create(array( - 'http' => array( - 'header' => "x-unl-ssi-depth: $ssiDepth\r\n", - ), - )); - - if ($ssiDepth > 3) { - watchdog('unl', 'Server Side Include: Recursion depth limit reached.', array(), WATCHDOG_ERROR); - drupal_add_http_header('x-unl-ssi-error', 'Too deep!'); - $content = '<!-- Error: Too many recursive includes! Content from ' . $url . ' was not included! -->'; + // If the varnish module is enabled, and the SSI is for a URL on our server, do an ESI. + if (module_exists('varnish') && + isset($_SERVER['HTTP_X_VARNISH']) && + gethostbyname($parts['host']) == gethostbyname($_SERVER['SERVER_NAME']) && + $parts['scheme'] == 'http' + ) { + $content = _unl_ssi_to_esi($url); } + // Otherwise, emulate the SSI in drupal. else { - $headers = array(); - $content = unl_url_get_contents($url, $context, $headers); - if (array_key_exists('x-unl-ssi-error', $headers)) { - watchdog('unl', 'Server Side Include: An included URL reached the depth limit.', array(), WATCHDOG_WARNING); - drupal_add_http_header('x-unl-ssi-error', 'The included URL caused recursion that was too deep!'); - } + $content = _unl_ssi_emulate($url); } + $replacements[$full_match] = PHP_EOL . '<!-- Begin content from ' . $url . ' -->' . PHP_EOL . $content . PHP_EOL @@ -1186,3 +1187,49 @@ function unl_filter_ssi_process($text, $filter, $format, $langcode, $cache, $cac return $text; } + +/** + * Used by unl_filter_ssi_process() to emulate the SSI process inside drupal. + * @param $url + * @return string + */ +function _unl_ssi_emulate($url) { + $ssiDepth = 0; + if (array_key_exists('HTTP_X_UNL_SSI_DEPTH', $_SERVER)) { + $ssiDepth = $_SERVER['HTTP_X_UNL_SSI_DEPTH']; + } + $ssiDepth++; + + $context = stream_context_create(array( + 'http' => array( + 'header' => "x-unl-ssi-depth: $ssiDepth\r\n", + ), + )); + + if ($ssiDepth > 3) { + watchdog('unl', 'Server Side Include: Recursion depth limit reached.', array(), WATCHDOG_ERROR); + drupal_add_http_header('x-unl-ssi-error', 'Too deep!'); + $content = '<!-- Error: Too many recursive includes! Content from ' . $url . ' was not included! -->'; + } + else { + $headers = array(); + $content = unl_url_get_contents($url, $context, $headers); + if (array_key_exists('x-unl-ssi-error', $headers)) { + watchdog('unl', 'Server Side Include: An included URL reached the depth limit.', array(), WATCHDOG_WARNING); + drupal_add_http_header('x-unl-ssi-error', 'The included URL caused recursion that was too deep!'); + } + } + + return $content; +} + +/** + * Used by unl_filter_ssi_process() to change the SSI into an ESI + * @param $url + * @return string + */ +function _unl_ssi_to_esi($url) { + // Set a header so that Varnish knows to do ESI processing on this response. + drupal_add_http_header('X-ESI', 'yes'); + return '<esi:include src="' . check_plain($url) . '"/>'; +} diff --git a/sites/all/modules/unl_cas/unl_cas.admin.inc b/sites/all/modules/unl_cas/unl_cas.admin.inc index 3a5347b5483da74997e925f450213934867d8a3b..b8e7157861ab5d1eeffee49d6e60fc1570c695f1 100644 --- a/sites/all/modules/unl_cas/unl_cas.admin.inc +++ b/sites/all/modules/unl_cas/unl_cas.admin.inc @@ -22,7 +22,7 @@ function unl_cas_user_import($form, &$form_state) { '#type' => 'submit', '#value' => 'Search', '#submit' => array('unl_cas_user_import_search'), -# '#validate' => array('unl_cas_user_validate'), + '#validate' => array('unl_cas_user_import_validate_search'), ); if (isset($form_state['values']['name'])) { @@ -93,22 +93,33 @@ function unl_cas_user_import($form, &$form_state) { '#type' => 'submit', '#value' => 'Add Selected User', '#submit' => array('unl_cas_user_import_submit'), + '#validate' => array('unl_cas_user_import_validate_add'), ); } return $form; } function unl_cas_user_import_search($form, &$form_state) { + if (!$form_state['values']['name']) { + form_set_error('username', 'Please enter a search string.'); + } // if only one result is returned should we instead create the user? $form_state['rebuild'] = TRUE; } -function unl_cas_user_import_submit($form, &$form_state) { +function unl_cas_user_import_validate_search($form, &$form_state) { + if (!$form_state['values']['name']) { + form_set_error('username', 'Please enter a search term.'); + } +} + +function unl_cas_user_import_validate_add($form, &$form_state) { if (!$form_state['values']['username']) { - drupal_set_message('Please select a user.', 'error'); - $form_state['rebuild'] = TRUE; - return; + form_set_error('username', 'Please select a user.'); } +} + +function unl_cas_user_import_submit($form, &$form_state) { $user = unl_cas_import_user($form_state['values']['username']); diff --git a/sites/all/modules/unl_cas/unl_cas.module b/sites/all/modules/unl_cas/unl_cas.module index 3bde41045e53b008eb2a039b02ad025977c04018..c133b3d8468214af49f754dfe665c41896b7b996 100644 --- a/sites/all/modules/unl_cas/unl_cas.module +++ b/sites/all/modules/unl_cas/unl_cas.module @@ -207,7 +207,7 @@ function unl_cas_user_logout($account) { function unl_cas_import_user($username) { unl_load_zend_framework(); $user = array(); - + $result = array(); // First, try getting the info from LDAP. try { $ldap = new Unl_Ldap(unl_cas_get_setting('ldap_uri')); @@ -215,42 +215,50 @@ function unl_cas_import_user($username) { $results = $ldap->search('dc=unl,dc=edu', 'uid=' . $username); if (count($results) > 0) { $result = $results[0]; - - $user['firstName'] = $result['givenname'][0]; - $user['lastName'] = $result['sn'][0]; - $user['email'] = $result['mail'][0]; - $user['displayName'] = $result['displayname'][0]; } } catch (Exception $e) { - // don't do anything, just go on to try the peoplefinder method + // don't do anything, just go on to try the PeopleFinder method } - // Next, if LDAP didn't work, try peoplefinder/directory service. - if (!isset($user['email'])) { - $xml = @file_get_contents('http://directory.unl.edu/service.php?format=xml&uid=' . $username); - if ($xml) { - $dom = new DOMDocument(); - $dom->loadXML($xml); - $user['firstName'] = $dom->getElementsByTagName('givenName')->item(0)->textContent; - $user['lastName'] = $dom->getElementsByTagName('sn')->item(0)->textContent; - $user['email'] = $dom->getElementsByTagName('mail')->item(0)->textContent; - $user['displayName'] = $dom->getElementsByTagName('displayName')->item(0)->textContent; + // Next, if LDAP didn't work, try PeopleFinder service. + if (!$result) { + $json = unl_url_get_contents('http://directory.unl.edu/service.php?format=json&uid=' . $username); + if ($json) { + $result = json_decode($json, TRUE); } } - // Finally, if peoplefinder didn't work either, just guess. - if (!isset($user['email'])) { - $user['email'] = $username . '@unl.edu'; - } - + // Create the fields we will be using, and make an initial guess at the email address. $userData = array( 'name' => $username, - 'mail' => $user['email'], + 'mail' => $username . '@unl.edu', 'status' => 1, - 'timezone' => variable_get('date_default_timezone', @date_default_timezone_get()), + 'timezone' => variable_get('date_default_timezone', date_default_timezone_get()), + 'data' => array('unl' => array( + 'fullName' => '', + 'affiliations' => '', + 'primaryAffiliation' => '', + 'department' => '', + 'major' => '', + 'studentStatus' => array(), + )), ); + // If either LDAP or PeopleFinder found data, use it. + if ($result) { + $result = array_change_key_case($result, CASE_LOWER); + $userData['mail'] = $result['mail'][0]; + $userData['data']['unl'] = array( + 'fullName' => (isset($result['edupersonnickname']) ? $result['edupersonnickname'][0] : $result['givenname'][0]) . ' ' . $result['sn'][0], + 'affiliations' => $result['edupersonaffiliation'], + 'primaryAffiliation' => $result['edupersonprimaryaffiliation'][0], + 'department' => (isset($result['unlhrprimarydepartment']) ? $result['unlhrprimarydepartment'][0] : ''), + 'major' => (isset($result['unlsismajor']) ? $result['unlsismajor'][0] : ''), + 'studentStatus' => (isset($result['unlsisstudentstatus']) ? $result['unlsisstudentstatus'] : array()), + ); + } + $account = user_load_by_name($username); return user_save($account, $userData); diff --git a/sites/all/modules/unl_migration/unl_migration.php b/sites/all/modules/unl_migration/unl_migration.php index 62d4d61312f143018866d3bd58e9fa1559afa323..f87a9be3ae6d0a08848bca32955222cb55001f92 100755 --- a/sites/all/modules/unl_migration/unl_migration.php +++ b/sites/all/modules/unl_migration/unl_migration.php @@ -168,16 +168,17 @@ class Unl_Migration_Tool private $_liferaySubsites = array( 'cropwatch.unl.edu' => array('corn', 'drybeans', 'forages', 'organic', 'potato', 'sorghum', 'soybeans', 'wheat', 'bioenergy', 'insect', 'economics', 'ssm', 'soils', 'tillage', 'weed', 'varietytest', 'biotechnology', 'farmresearch', 'cropwatch-youth', 'militaryresources', 'gaps', 'sugarbeets'), - '4h.unl.edu' => array('extension-4-h-horse', '4hcamps', '4hcurriclum'), - 'animalscience.unl.edu' => array('fernando-lab', 'anscgenomics', 'rprb-lab', 'ruminutrition-lab'), + '4h.unl.edu' => array('extension-4-h-horse'), + 'animalscience.unl.edu' => array('fernando-lab', 'anscgenomics', 'rprb-lab', 'ruminutrition-lab', 'pre-vet-program'), 'beef.unl.edu' => array('cattleproduction'), 'biochem.unl.edu' => array('barycki', 'bailey', 'becker', 'adamec', 'wilson', 'biochem-fatttlab', 'simpson'), 'bse.unl.edu' => array('p2guidelines'), 'edmedia.unl.edu' => array('techtraining'), 'food.unl.edu' => array('localfoods', 'allergy', 'fnh', 'preservation', 'fpc', 'safety', 'meatproducts', 'youth'), - 'ianrhome.unl.edu' => array('ianrinternational'), + 'ianrhome.unl.edu' => array('ianrinternational', 'liaison'), 'water.unl.edu' => array('crops', 'cropswater', 'drinkingwater', 'drought', 'wildlife', 'hydrology', 'lakes', 'landscapes', 'landscapewater', 'laweconomics', 'manure', 'propertydesign', 'research', 'sewage', 'students', 'watershed', 'wells', 'wetlands'), 'westcentral.unl.edu' => array('wcentomology', 'wcacreage'), + 'agecon.unl.edu' => array('policy'), ); /** @@ -1020,13 +1021,17 @@ class Unl_Migration_Tool $pathParts = explode('/', ltrim($urlParts['path'], '/')); $siteNameMap = array( - 'extension' => 'www.extension.unl.edu', - 'webster' => 'www.webster.unl.edu', + 'anisci' => 'animalscience.unl.edu', + 'extension' => 'www.extension.unl.edu', + 'ianr' => 'ianrhome.unl.edu', + 'webster' => 'www.webster.unl.edu', + 'vetscience' => 'vbms.unl.edu', ); if ( count($pathParts) >= 2 && $pathParts[0] == 'web' && !(in_array($urlParts['host'], array_keys($this->_liferaySubsites)) && in_array($pathParts[1], $this->_liferaySubsites[$urlParts['host']])) + && substr(parse_url($this->_baseUrl, PHP_URL_PATH), 0, 5) != '/web/' ) { // If the site name is "special" look it up in the map. Otherwise, just add .unl.edu diff --git a/sites/all/modules/unl_multisite/unl_site_creation.php b/sites/all/modules/unl_multisite/unl_site_creation.php index 0518d9028143c938a76e5d46d9ea50a54d552b82..ffbcfbba1520cad47edfab901d3b4425faa943c9 100644 --- a/sites/all/modules/unl_multisite/unl_site_creation.php +++ b/sites/all/modules/unl_multisite/unl_site_creation.php @@ -663,6 +663,26 @@ function unl_site_alias_create_validate($form, &$form_state) { if (substr($form_state['values']['path'], 0, 1) == '/') { $form_state['values']['path'] = substr($form_state['values']['path'], 1); } + + // Check that the alias does not already exist. + $query = db_select('unl_sites_aliases', 'a'); + $query->fields('a', array('base_uri', 'path')); + + $db_or = db_or(); + $db_or->condition('a.path', $form_state['values']['path'], '='); + // Also consider legacy aliases that do not have a trailing slash. + $db_or->condition('a.path', substr($form_state['values']['path'], 0, -1), '='); + + $db_and = db_and(); + $db_and->condition('a.base_uri', $form_state['values']['base_uri'], '='); + $db_and->condition($db_or); + + $query->condition($db_and); + $result = $query->execute()->fetchAssoc(); + + if ($result) { + form_set_error('alias_path', t('Site alias already exists.')); + } } /** @@ -787,16 +807,16 @@ function unl_page_alias_create($form, &$form_state) { ); $form['root']['from_uri'] = array( '#type' => 'textfield', - '#title' => t('From URL'), - '#description' => t('The URL that users will visit.'), - '#default_value' => url('from/url', array('https' => FALSE)), + '#title' => t('From URI'), + '#description' => t('The URI that users will visit.'), + '#default_value' => url('from/uri', array('https' => FALSE)), '#required' => TRUE, ); $form['root']['to_uri'] = array( '#type' => 'textfield', - '#title' => t('To URL'), - '#description' => t('The URL users will be redirected to.'), - '#default_value' => url('to/url', array('https' => FALSE)), + '#title' => t('To URI'), + '#description' => t('The URI users will be redirected to.'), + '#default_value' => url('to/uri', array('https' => FALSE)), '#required' => TRUE, ); $form['root']['submit'] = array( @@ -820,12 +840,23 @@ function unl_page_alias_create_validate($form, &$form_state) { if (parse_url($from, PHP_URL_HOST) == parse_url($to, PHP_URL_HOST) && parse_url($from, PHP_URL_PATH) == parse_url($to, PHP_URL_PATH) && parse_url($from, PHP_URL_QUERY) == parse_url($to, PHP_URL_QUERY)) { - form_set_error('to_uri', 'From URL cannot equal To URL.'); + form_set_error('to_uri', 'From URI cannot equal To URI.'); } if (parse_url($from, PHP_URL_HOST) == parse_url($root, PHP_URL_HOST) && parse_url($from, PHP_URL_PATH) == parse_url($root, PHP_URL_PATH) && parse_url($from, PHP_URL_QUERY) == parse_url($root, PHP_URL_QUERY)) { - form_set_error('from_uri', 'From URL cannot be the root of the default site.'); + form_set_error('from_uri', 'From URI cannot be the root of the default site.'); + } + + // Check that the alias from_uri does not already exist. + $query = db_select('unl_page_aliases', 'a'); + $query->fields('a', array('from_uri', 'to_uri')); + + $query->condition('a.from_uri', $form_state['values']['from_uri'], '='); + $result = $query->execute()->fetchAssoc(); + + if ($result) { + form_set_error('alias_path', t('Page alias From URI already exists.')); } } @@ -856,7 +887,7 @@ function unl_page_alias_list($form, &$form_state) { 'data' => t('Status'), 'field' => 'a.installed', ), - 'remove' => t('Remove (can not undo!)'), + 'remove' => t('Remove'), ); $query = db_select('unl_page_aliases', 'a') @@ -1144,7 +1175,7 @@ function unl_get_site_user_map($search_by, $username_or_role, $list_empty_sites ); } catch (Exception $e) { // Either the site has no settings.php or the db_prefix is wrong. - drupal_set_message('Error querying database for site ' . $site->uri, 'warning'); + watchdog('unl_multisite', 'Error querying database for site %uri', array('%uri' => $site->uri), WATCHDOG_WARNING); } } diff --git a/sites/all/modules/unl_varnish/unl_varnish.module b/sites/all/modules/unl_varnish/unl_varnish.module index 36dcb1e9dcc40cb38bf22d3e613d1cf71b7f86da..d58938ac375eed1f2943d804dea067fcd3084d4e 100644 --- a/sites/all/modules/unl_varnish/unl_varnish.module +++ b/sites/all/modules/unl_varnish/unl_varnish.module @@ -60,7 +60,7 @@ function unl_varnish_purge_submit($form, &$form_state) { */ function unl_varnish_purge_all_sites_submit($form, &$form_state) { $path = $form_state['values']['varnish_path']; - _varnish_terminal_run("purge.url $path"); + varnish_purge('.*', $path); drupal_set_message("Varnish purged paths matching $path.", 'status'); } @@ -150,6 +150,6 @@ function unl_varnish_purge_all_pages() { $host = parse_url($alias, PHP_URL_HOST); $path = parse_url($alias, PHP_URL_PATH); - _varnish_terminal_run("purge req.http.host ~ $host && req.url ~ ^$path"); + varnish_purge($host, $path); } } diff --git a/sites/all/modules/upload_replace/upload_replace.info b/sites/all/modules/upload_replace/upload_replace.info index e14ce8e8e0916b5f78364b4cd9df6c2ed41bf13e..a295b84ba7decbc80ec55b81e4b70c22564f4196 100644 --- a/sites/all/modules/upload_replace/upload_replace.info +++ b/sites/all/modules/upload_replace/upload_replace.info @@ -2,7 +2,9 @@ name = Upload replace description = Make the most recent version of a file always retain its original filename, older file with the same name are renamed by drupal standards "_0, _1, etc" core = 7.x files[] = upload_replace.module -; patched with http://drupal.org/files/upload_replace_error-1115484-13.patch -version = "7.x-1.0-beta1-patched" +; Information added by drupal.org packaging script on 2011-12-15 +version = "7.x-1.0-beta1" core = "7.x" project = "upload_replace" +datestamp = "1323909946" + diff --git a/sites/all/modules/viewreference/LICENSE.txt b/sites/all/modules/viewreference/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/sites/all/modules/viewreference/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/viewreference/README.txt b/sites/all/modules/viewreference/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..dea9b123cb2131c619c6890f4b75b76b258ddd0e --- /dev/null +++ b/sites/all/modules/viewreference/README.txt @@ -0,0 +1,31 @@ + +View reference README + +CONTENTS OF THIS FILE +---------------------- + + * Introduction + * Installation + * Usage + + +INTRODUCTION +------------ +Defines a field type View reference which creates a relationship to a Views +display and allows the view to be displayed as the content of the field. + +Project page: http://drupal.org/project/viewereference. + + +INSTALLATION +------------ +Install and enable the View reference module. +For detailed instructions on installing contributed modules see: +http://drupal.org/documentation/install/modules-themes/modules-7 + + +USAGE +----- +View reference fields will now be available in the Field UI. +For detailed instructions on using the Field UI see: +http://drupal.org/documentation/modules/field-ui \ No newline at end of file diff --git a/sites/all/modules/viewreference/viewreference.info b/sites/all/modules/viewreference/viewreference.info new file mode 100644 index 0000000000000000000000000000000000000000..304082c0f456a22c73ec46d0d0f66d2fe0930935 --- /dev/null +++ b/sites/all/modules/viewreference/viewreference.info @@ -0,0 +1,11 @@ +name = View reference +description = Defines a field type for referencing a view from a node. +package = Fields +core = 7.x +dependencies[] = views +; Information added by drupal.org packaging script on 2012-08-06 +version = "7.x-3.4" +core = "7.x" +project = "viewreference" +datestamp = "1344228442" + diff --git a/sites/all/modules/viewreference/viewreference.install b/sites/all/modules/viewreference/viewreference.install new file mode 100644 index 0000000000000000000000000000000000000000..7192ad69c55568e3f46a2f0a4d1caced3d39ffd5 --- /dev/null +++ b/sites/all/modules/viewreference/viewreference.install @@ -0,0 +1,14 @@ +<?php + +/** + * @file + * View reference install file. + */ + +/** + * Implements hook_update_last_removed(). + */ +function viewreference_update_last_removed() { + // Removed update 6300 because code relied on CCK tables. + return 6300; +} \ No newline at end of file diff --git a/sites/all/modules/viewreference/viewreference.module b/sites/all/modules/viewreference/viewreference.module new file mode 100644 index 0000000000000000000000000000000000000000..d9a8a69daa297285ccbd94f7a89a04e947cef6bb --- /dev/null +++ b/sites/all/modules/viewreference/viewreference.module @@ -0,0 +1,955 @@ +<?php + +/** + * @file + * Defines a field type for referencing a view from a node. + */ + +/** + * Implements hook_menu(). + */ +function viewreference_menu() { + $items = array(); + $items['viewreference/autocomplete/%/%'] = array( + 'title' => 'viewreference autocomplete', + 'page callback' => 'viewreference_autocomplete', + 'page arguments' => array(2, 3), + 'access callback' => 'viewreference_autocomplete_access', + 'access arguments' => array(2, 3), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Implements hook_theme(). + */ +function viewreference_theme() { + return array( + 'viewreference_display_title' => array( + 'variables' => array( + 'view' => NULL, + 'view_name' => NULL, + 'display_key' => NULL, + 'append_id' => FALSE, + ), + ), + 'viewreference_formatter_default' => array( + 'variables' => array('element' => NULL), + ), + 'viewreference_formatter_full' => array( + 'variables' => array('element' => NULL), + ), + 'viewreference_formatter_plain' => array( + 'variables' => array('element' => NULL), + ), + 'viewreference_formatter_link' => array( + 'variables' => array('element' => NULL), + ), + 'viewreference_formatter_path' => array( + 'variables' => array('element' => NULL), + ), + ); +} + +/** + * Implements hook_field_info(). + */ +function viewreference_field_info() { + $field_info = array( + 'viewreference' => array( + 'label' => t('View reference'), + 'description' => t('Reference a views display from the views module.'), + 'settings' => array( + 'referenceable_views' => array(), + 'referenceable_tags' => array( + 'allow' => '', + 'deny' => '', + ), + 'arguments' => array( + 'dsv_arguments' => 0, + 'php_arguments' => 0, + 'delimiter' => '/', + 'rows' => 1, + 'label' => '!field_label ' . t('arguments'), + ), + 'append_id' => 0, + 'skip_default' => 1, + 'skip_empty' => 0, + ), + 'default_widget' => 'viewreference_autocomplete', + 'default_formatter' => 'viewreference_default', + ), + ); + return $field_info; +} + +/** + * Implements hook_field_schema(). + */ +function viewreference_field_schema($field) { + $columns = array( + 'view_id' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => NULL, + 'not null' => FALSE, + ), + 'arguments' => array( + 'type' => 'text', + 'size' => 'big', + ), + ); + + return array( + 'columns' => $columns, + 'indexes' => array('view_id' => array('view_id')), + ); +} + +/** + * Implements hook_field_settings_form(). + */ +function viewreference_field_settings_form($field, $instance, $has_data) { + $settings = $field['settings']; + + $form = array(); + $form['referenceable_views'] = array( + '#type' => 'checkboxes', + '#title' => t('Views that can be referenced'), + '#description' => t('Select the Views that can be referenced. If no Views are selected here, and tags are not entered below, then all the Views will be available.'), + '#multiple' => TRUE, + '#default_value' => is_array($settings['referenceable_views']) ? $settings['referenceable_views'] : array(), + '#options' => viewreference_get_views($settings['append_id'], $settings['skip_default']), + ); + $form['referenceable_tags'] = array( + '#type' => 'fieldset', + '#title' => t('Views tags that can be referenced'), + '#collapsible' => TRUE, + '#collapsed' => (!empty($settings['referenceable_tags']['allow']) || !empty($settings['referenceable_tags']['deny'])) ? FALSE : TRUE, + '#description' => t('Optionally you can allow views by tags here instead of, or in addition to, using <em>Views that can be referenced</em>, then other Views will be excluded. You can also deny views by tags, and if <em>Views that can be referenced</em> and the <em>Allow list</em> are left blank all other Views will be available.'), + ); + $form['referenceable_tags']['allow'] = array( + '#type' => 'textfield', + '#title' => t('Allow list'), + '#default_value' => !empty($settings['referenceable_tags']['allow']) ? $settings['referenceable_tags']['allow'] : '', + '#size' => 128, + '#maxlength' => 512, + '#description' => t('Enter a comma delimited list of tags to include.'), + ); + $form['referenceable_tags']['deny'] = array( + '#type' => 'textfield', + '#title' => t('Deny list'), + '#default_value' => !empty($settings['referenceable_tags']['deny']) ? $settings['referenceable_tags']['deny'] : '', + '#size' => 128, + '#maxlength' => 512, + '#description' => t('Enter a comma delimited list of tags to exclude.'), + ); + $form['arguments'] = array( + '#type' => 'fieldset', + '#title' => t('Contextual filter arguments'), + '#collapsible' => TRUE, + '#collapsed' => ($settings['arguments']['dsv_arguments'] || $settings['arguments']['php_arguments']) ? FALSE : TRUE, + '#description' => t('Enabling the following options will provide an input field for passing arguments (aka <em>contextual filters</em>) to the View.'), + ); + $form['arguments']['dsv_arguments'] = array( + '#type' => 'checkbox', + '#title' => t('Allow delimiter seperated values.'), + '#default_value' => isset($settings['arguments']['dsv_arguments']) ? $settings['arguments']['dsv_arguments'] : 0, + '#description' => t('Users can provide a list of arguments seperated by a delimiter. e.g: <em>term_1/term_2</em>'), + ); + $form['arguments']['php_arguments'] = array( + '#type' => 'checkbox', + '#title' => t('Allow PHP code.'), + '#default_value' => isset($settings['arguments']['php_arguments']) ? $settings['arguments']['php_arguments'] : 0, + '#description' => t('Users can insert PHP code to generate the list of arguments. e.g: <em>term_1/<?php print "term_x/term_y"; ?>/term_2</em>'), + ); + $form['arguments']['delimiter'] = array( + '#type' => 'textfield', + '#title' => t('Delimiter'), + '#default_value' => !empty($settings['arguments']['delimiter']) ? $settings['arguments']['delimiter'] : '/', + '#size' => 3, + '#maxlength' => 30, + '#required' => TRUE, + ); + $row_range = range(0, 10); + unset($row_range[0]); + $form['arguments']['rows'] = array( + '#type' => 'select', + '#title' => t('Number of rows in argument field'), + '#default_value' => isset($settings['arguments']['rows']) ? $settings['arguments']['rows'] : 1, + '#options' => $row_range, + '#description' => t('Set as 1 for textfield, or larger values for textarea (may be easier to write PHP with a textarea)'), + '#required' => TRUE, + ); + $form['arguments']['label'] = array( + '#type' => 'textfield', + '#title' => t('Label for arguments field'), + '#default_value' => isset($settings['arguments']['label']) ? $settings['arguments']['label'] : '!field_label ' . t('arguments'), + '#description' => t('Use <em>!field_label</em> to insert the field label.'), + ); + $form['append_id'] = array( + '#type' => 'checkbox', + '#title' => t('Append unique ID in lists.'), + '#default_value' => isset($settings['append_id']) ? $settings['append_id'] : 0, + '#description' => t('It is possible for Views displays to have the same title, this option will append [view:view_display_n] in lists used by this field to disambiguate the options.'), + ); + $form['skip_default'] = array( + '#type' => 'checkbox', + '#title' => t('Exclude the master (default) display from lists.'), + '#default_value' => isset($settings['skip_default']) ? $settings['skip_default'] : 1, + ); + $form['skip_empty'] = array( + '#type' => 'checkbox', + '#title' => t('Do not output view if no rows are detected'), + '#default_value' => isset($settings['skip_empty']) ? $settings['skip_empty'] : 0, + ); + return $form; + +} + +/** + * Implements hook_field_validate(). + */ +function viewreference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { + $views = viewreference_get_views($field['settings']['append_id'], $field['settings']['skip_default'], $field['settings']); + foreach ($items as $delta => $item) { + if (is_array($item)) { + if (!empty($item['view_id'])) { + if (!in_array($item['view_id'], array_keys($views))) { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'invalid_view', + 'message' => t("%name: This view can't be referenced.", + array('%name' => $instance['label'])), + ); + } + } + } + } +} + +/** + * Implements hook_field_is_empty(). + */ +function viewreference_field_is_empty($item, $field) { + return empty($item['view_id']); +} + +/** + * Implements hook_field_formatter_info(). + */ +function viewreference_field_formatter_info() { + $ret = array( + 'viewreference_default' => array( + 'label' => t('Default (view)'), + 'description' => t('Display the referenced view.'), + 'field types' => array('viewreference'), + ), + 'viewreference_full' => array( + 'label' => t('Full (title and view)'), + 'description' => t('Display the referenced view with a title.'), + 'field types' => array('viewreference'), + ), + 'viewreference_plain' => array( + 'label' => t('Title (no link)'), + 'description' => t('Display the title of the referenced view.'), + 'field types' => array('viewreference'), + ), + 'viewreference_link' => array( + 'label' => t('Title (link)'), + 'description' => t('Display the title of the referenced view as a link (if possible).'), + 'field types' => array('viewreference'), + ), + 'viewreference_path' => array( + 'label' => t('Path'), + 'description' => t('Display the path of the referenced view (if possible).'), + 'field types' => array('viewreference'), + ), + ); + return $ret; +} + +/** + * Implements hook_field_formatter_view(). + */ +function viewreference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + $formatter = str_replace('viewreference_', '', $display['type']); + foreach ($items as $delta => $item) { + $element[$delta] = array( + '#theme' => 'viewreference_formatter_' . $formatter, + '#element' => array( + 'entity_type' => &$entity_type, + 'entity' => &$entity, + 'field' => &$field, + 'instance' => &$instance, + 'langcode' => &$langcode, + 'item' => $item, + 'display' => &$display, + ), + ); + $element[$delta]['#element']['view'] = viewreference_get_view($element[$delta]['#element']); + if (!$element[$delta]['#element']['view']) { + unset($element[$delta]); + } + } + return $element; +} + +/** + * Value callback for a viewreference_autocomplete element. + */ +function viewreference_autocomplete_value($element, $input = FALSE, $form_state) { + if ($input === FALSE) { + $field_key = $element['#columns'][0]; + $args_key = $element['#columns'][1]; + if (!empty($element['#default_value'][$field_key])) { + $field_name = $element['#parents'][0]; + $field = field_info_field($field_name); + $views = viewreference_get_views(TRUE, $field['settings']['skip_default'], array('referenceable_views' => array($element['#default_value'][$field_key]))); + $value = $views[$element['#default_value'][$field_key]]; + return array($field_key => $value, $args_key => $element['#default_value'][$args_key]); + } + return array($field_key => NULL, $args_key => $element['#default_value'][$args_key]); + } +} + +/** + * Validation callback for a viewreference_autocomplete element. + */ +function viewreference_autocomplete_validate($element, &$form_state, $form) { + $field = $form_state['field'][$element['#field_name']][$element['#language']]['field']; + $field_key = $element['#columns'][0]; + $value = $element['#value']; + $new_value = NULL; + + if (!empty($value)) { + $regex = '/' // Start of string + . '(.*).*?' // Any non-greedy + . '(\\[)' // [ + . '((?:[a-z][a-z0-9_]*))' // Variable 1 + . '(:)' // : + . '((?:[a-z][a-z0-9_]*))' // Variable 2 + . '(\\])' // ] + . '/is'; // End of string + preg_match($regex, $value, $matches); + if (!empty($matches)) { + $new_value = $matches[3] . ':' . $matches[5]; + $allowed = viewreference_get_views(FALSE, $field['settings']['skip_default'], $field['settings']); + if (!isset($allowed[$new_value])) { + form_error( + $element, + t( + '%name: View display %value cannot be referenced.', + array( + '%name' => $element['#title'], + '%value' => $new_value, + ) + ) + ); + } + } + else { + form_error( + $element, + t( + '%name: The value %value is in an unexpected format. Expecting: %format', + array( + '%name' => $element['#title'], + '%value' => $value, + '%format' => '<em>View title [view:display_n]</em>', + ) + ) + ); + } + } + + form_set_value($element, $new_value, $form_state); +} + +/** + * Implements hook_field_widget_error(). + */ +function viewreference_field_widget_error($element, $error, $form, &$form_state) { + form_error($element['view_id'], $error['message']); +} + +/** + * Menu access callback for the autocomplete path. + * + * Check for both 'edit' and 'view' access in the unlikely event + * a user has edit but not view access. + */ +function viewreference_autocomplete_access($entity_type, $field_name) { + return user_access('access content') && ($field = field_info_field($field_name)) && field_access('view', $field, $entity_type) && field_access('edit', $field, $entity_type); +} + +/** + * Menu callback for the autocomplete results. + */ +function viewreference_autocomplete($bundle_name, $field_name, $string = '') { + // If the request has a '/' in the search text, then the menu system will have + // split it into multiple arguments, recover the intended $string. + $args = func_get_args(); + // Shift off the $bundle_name argument. + array_shift($args); + // Shift off the $field_name argument. + array_shift($args); + $string = implode('/', $args); + + $field = field_info_field($field_name); + $settings = $field['settings']; + $matches = viewreference_get_views($settings['append_id'], $settings['skip_default'], $settings, FALSE, $string, FALSE, TRUE); + drupal_json_output($matches); +} + +/** + * Implements hook_options_list(). + */ +function viewreference_options_list($field) { + $references = viewreference_get_views($field['settings']['append_id'], $field['settings']['skip_default'], $field['settings']); + $options = array(); + foreach ($references as $key => $value) { + $options[$key] = $value['rendered']; + } + return $options; +} + +/** + * Create a display title for a view display. + */ +function viewreference_views_display_title($view_name, $view, $display) { + // Build the display title. + if (isset($view->display[$display]->display_options['title'])) { + // This view display has a nice title, let's use that. + return $view->display[$display]->display_options['title']; + } + elseif (isset($view->display['default']->display_options['title'])) { + // This view display inherits it's title from the default display. + return $view->display['default']->display_options['title']; + } + else { + // This view display does not have a title configured, we have to construct a title. + return ucfirst($view_name) . ' ' . strtolower($view->display[$display]->display_title); + } +} + +/** + * Get an array of data and rendered HTML items that are useful in theming the + * formatter output. + * + * @param $element + * An array of parameters used in viewing the element. + * @param $params + * An array of special features needed to be built: + * 'embed' - The $view->preview() of the view display + * 'title' - The display title of the view display + * 'link' - A link to the view display, or the title if link not available. + * 'path' - The path to the view display, or the title if path not available. + * 'contextual' - The contextual links. + * @return + * The array of useful data about the view. + */ +function viewreference_get_view($element, $params = array()) { + if (!empty($element['item']['view_id'])) { + if (!empty($element['view'])) { + $view = $element['view']; + } + if (empty($view['view']) || empty($view['display']) || empty($view['name'])) { + $view_id_parts = explode(':', $element['item']['view_id']); + $view['name'] = $view_id_parts[0]; + $view['display'] = $view_id_parts[1]; + $view['view'] = views_get_view($view['name']); + } + if ($view['view'] && $view['view']->access($view['display'])) { + // Save $_GET['q'] so it can be restored before returning from this function. + $q = $_GET['q']; + if (empty($view['embed']) && (in_array('embed', $params) || !empty($element['field']['settings']['skip_empty']))) { + $view['args'] = viewreference_get_element_args($element); + if (!empty($view['args'])) { + $_GET['q'] .= '/' . implode('/', $view['args']); + } + $view['embed'] = $view['view']->preview($view['display'], $view['args']); + if (!empty($element['field']['settings']['skip_empty']) && empty($view['view']->result)) { + return FALSE; + } + } + if ((in_array('title', $params) && empty($view['title'])) || (in_array('link', $params) && empty($view['link']))) { + $view['title'] = theme('viewreference_display_title', $view); + if (in_array('link', $params) && empty($view['link'])) { + $disabled = isset($view['view']->disabled) ? $view['view']->disabled: FALSE; + if (isset($view['view']->display[$view['display']]->display_options['path']) && !$disabled) { + $view['args'] = isset($view['args']) ? $view['args'] : viewreference_get_element_args($element); + $view['url_args'] = implode('/', $view['args']); + $view['path'] = $view['view']->display[$view['display']]->display_options['path']; + if ($view['url_args']) { + $view['path'] .= '/' . $view['url_args']; + } + $view['link'] = l($view['title'], $view['view']->get_url($view['args'], $view['path'])); + } + else { + $view['link'] = $view['title']; + } + } + } + + if (in_array('contextual', $params) && empty($view['contextual'])) { + $view['contextual'] = ''; + if (module_exists('contextual')) { + $contextual = contextual_element_info(); + views_add_contextual_links($contextual['contextual_links'], 'special_block_-exp', $view['view'], $view['display']); + if (!empty($contextual['contextual_links']['#contextual_links'])) { + $view['contextual'] = drupal_render($contextual['contextual_links']); + } + } + } + + $_GET['q'] = $q; + return $view; + } + } +} + +/** + * Theme function for 'default' viewreference field formatter. + */ +function theme_viewreference_formatter_default($variables) { + $element = &$variables['element']; + $output = ''; + if ($view = viewreference_get_view($element, array('contextual', 'embed'))) { + $output .= '<div class="contextual-links-region">'; + $output .= $view['contextual']; + $output .= $view['embed']; + $output .= '</div>'; + } + return $output; +} + +/** + * Theme function for 'full' viewreference field formatter. + */ +function theme_viewreference_formatter_full($variables) { + $element = &$variables['element']; + $output = ''; + if ($view = viewreference_get_view($element, array('contextual', 'title', 'embed'))) { + $output .= '<div class="contextual-links-region">'; + $output .= '<h3 class="title viewreference-title">' . $view['title'] . '</h3>'; + $output .= $view['contextual']; + $output .= $view['embed']; + $output .= '</div>'; + } + return $output; +} + +/** + * Theme function for 'plain' viewreference field formatter. + */ +function theme_viewreference_formatter_plain($variables) { + $element = &$variables['element']; + $output = ''; + if ($view = viewreference_get_view($element, array('title'))) { + $output .= $view['title']; + } + return $output; +} + +/** + * Theme function for 'link' viewreference field formatter. + */ +function theme_viewreference_formatter_link($variables) { + $element = &$variables['element']; + $output = ''; + if ($view = viewreference_get_view($element, array('link'))) { + $output .= $view['link']; + } + return $output; +} + +/** + * Theme function for 'path' viewreference field formatter. + */ +function theme_viewreference_formatter_path($variables) { + $element = &$variables['element']; + $output = ''; + if ($view = viewreference_get_view($element, array('link'))) { + $output .= isset($view['path']) ? $view['path'] : $view['link']; + } + return $output; +} + +/** + * Get an array of views. + * + * @param $append_id + * Whether to append the id to the returned display names. + * @param $skip_default + * Whether to omit default/master displays. + * @param $settings + * If applying filters, the settings of the field. + * @param $full + * If TRUE will return all the data, rather than just the title. + * @param $string + * String to match against the title to filter results by. + * @param $exact_string + * If TRUE the $string parameter must match exactly. + * @param $long_key + * If TRUE will key array by the title and ID, not just the ID. + * + * @return + * The array of views. + */ +function viewreference_get_views($append_id = FALSE, $skip_default = TRUE, $settings = NULL, $full = FALSE, $string = '', $exact_string = FALSE, $long_key = FALSE) { + $views = array(); + $loaded_views = views_get_all_views(); + + // Prepare filters. + if (!empty($settings['referenceable_views'])) { + $filters['ids'] = array_filter($settings['referenceable_views']); + } + if (!empty($settings['referenceable_tags']['allow'])) { + $filters['allow_tags'] = array_map('trim', explode(',', $settings['referenceable_tags']['allow'])); + } + if (!empty($settings['referenceable_tags']['deny'])) { + $filters['deny_tags'] = array_map('trim', explode(',', $settings['referenceable_tags']['deny'])); + } + + foreach ((array)$loaded_views as $view_name => $view) { + + // Prepare this view's tags. + $tags = array_map('trim', explode(',', $view->tag)); + + // Determine if there are allow tags + $has_allow_tag = FALSE; + if (!empty($tags) && !empty($filters['allow_tags'])) { + foreach ($tags as $tag) { + if (in_array($tag, $filters['allow_tags'])) { + $has_allow_tag = TRUE; + break; + } + } + } + + // Determine if there are deny tags + $has_deny_tag = FALSE; + if (!empty($tags) && !empty($filters['deny_tags'])) { + foreach ($tags as $tag) { + if (in_array($tag, $filters['deny_tags'])) { + $has_deny_tag = TRUE; + break; + } + } + } + + foreach ((array)$view->display as $display_key => $display) { + // Skip this one if it's a 'default' view and we're skipping defaults. + if ($display_key != 'default' || !$skip_default) { + $id = $view_name . ':' . $display_key; + + // Skip this one if it's not 'allowed'. + if ( + ( + (!empty($filters['ids']) && in_array($id, $filters['ids'])) || $has_allow_tag || (empty($filters['ids']) && empty($filters['allow_tags'])) + ) + && !$has_deny_tag + ) { + // Get display title. + $theme_vars = array( + 'view' => $view, + 'view_name' => $view_name, + 'display_key' => $display_key, + 'append_id' => $append_id, + ); + $display_title = theme('viewreference_display_title', $theme_vars); + // Determine whether and what to return. + $key = $long_key ? $display_title . ($append_id ? '' : ' [' . $id . ']') : $id; + if ($string) { + if (!$exact_string && (stripos($display_title, $string) !== FALSE || stripos($key, $string) !== FALSE)) { + $views[$key] = $full ? $display : $display_title; + } + elseif ($display_title == $string) { + $views[$key] = $full ? $display : $display_title; + } + } + else { + $views[$key] = $full ? $display : $display_title; + } + } + } + } + } + return $views; +} + +/** + * Theme the display title for this view display. + * + * @param $variables + An array of arguments that will be extracted to the following variables: + * $view - The view object. + * $view_name - The name of the view. + * $display_key - The name of the display to use. + * $append_id - Boolean indicating whether to append a unique id. + * @return + * The display title of this views display. + */ +function theme_viewreference_display_title($variables) { + // Get variables passed to theme function. + extract($variables); + + $view->set_display($display_key); + $display_title = $view->get_title(); + + if (!$display_title) { + // No title, we have to construct a title. + $display_title = ucfirst($view_name) . ' ' . strtolower($view->display[$display_key]->display_title); + } + + if ($append_id) { + // Append ID for disambiguation in forms (views displays can have the same title). + $display_title .= ' [' . $view_name . ':' . $display_key . ']'; + } + + return $display_title; +} + +/** + * Convert arguments text field entry to an array of arguments. + */ +function viewreference_get_element_args($element) { + $string = &$element['item']['arguments']; + $field = &$element['field']; + $settings = $field['settings']; + $delimiter = &$settings['arguments']['delimiter']; + $dsv_arguments = &$settings['arguments']['dsv_arguments']; + $php_arguments = &$settings['arguments']['php_arguments']; + $arguments = ''; + $args = array(); + if ($php_arguments) { + $variables = array( + $element['entity_type'] => $element['entity'], + ); + $arguments = viewreference_eval($string, $variables); + } + elseif ($dsv_arguments) { + $arguments = $string; + } + if ($arguments) { + $args = explode($delimiter, $arguments); + foreach ($args as $k => $v) { + $args[$k] = trim($v); + } + } + // Allow modules to easily supply their own views arguments. + drupal_alter('viewreference_args', $args, $element); + return $args; +} + +/** + * A version of php_eval() that allows passing of variables. + */ +function viewreference_eval($code, $variables = array()) { + global $theme_path, $theme_info, $conf; + + // Store current theme path. + $old_theme_path = $theme_path; + + // Restore theme_path to the theme, as long as drupal_eval() executes, + // so code evaluted will not see the caller module as the current theme. + // If theme info is not initialized get the path from theme_default. + if (!isset($theme_info)) { + $theme_path = drupal_get_path('theme', $conf['theme_default']); + } + else { + $theme_path = dirname($theme_info->filename); + } + + foreach ((array)$variables as $key => $value) { + $$key = $value; + } + + ob_start(); + print eval('?>' . $code); + $output = ob_get_contents(); + ob_end_clean(); + + // Recover original theme path. + $theme_path = $old_theme_path; + + return $output; +} + +/** + * Implements hook_field_widget_info(). + */ +function viewreference_field_widget_info() { + return array( + 'viewreference_select' => array( + 'label' => t('Select list'), + 'description' => t('Display the list of referenceable views as a select list.'), + 'field types' => array('viewreference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + 'viewreference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'description' => t('Display the list of referenceable views as a textfield with autocomplete behaviour.'), + 'field types' => array('viewreference'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_DEFAULT, + ), + ), + ); +} + +/** + * Implements hook_field_widget_form(). + */ +function viewreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + switch ($instance['widget']['type']) { + case 'viewreference_select': + $element = array( + '#type' => 'viewreference_select', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + ); + break; + case 'viewreference_autocomplete': + $element = array( + '#type' => 'viewreference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'viewreference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Implements hook_element_info(). + */ +function viewreference_element_info() { + return array( + 'viewreference_select' => array( + '#input' => TRUE, + '#columns' => array('view_id', 'arguments'), + '#delta' => 0, + '#process' => array('viewreference_select_process'), + ), + 'viewreference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('view_id', 'arguments'), + '#delta' => 0, + '#process' => array('viewreference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Process callback for a viewreference_select element. + * + * @see viewreference_element_info(). + */ +function viewreference_select_process($element, $form_state, $form) { + $field_name = $element['#parents'][0]; + $language = $element['#parents'][1]; + $field = $form_state['field'][$field_name]; + $instance = $field[$language]['instance']; + $bundle = $instance['bundle']; + $settings = $field[$language]['field']['settings']; + + $options = viewreference_get_views($settings['append_id'], $settings['skip_default'], $settings); + if (!$instance['required']) { + $options = array(0 => '<'. t('none') .'>') + $options; + } + + $element[$element['#columns'][0]] = array( + '#type' => 'select', + '#multiple' => 0, + '#options' => $options, + '#default_value' => isset($element['#value'][$element['#columns'][0]]) ? $element['#value'][$element['#columns'][0]] : '', + '#field_name' => $field_name, + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#title' => $instance['label'], + '#required' => $instance['required'], + '#description' => isset($instance['description']) ? $instance['description'] : NULL, + ); + + if ($settings['arguments']['dsv_arguments'] || $settings['arguments']['php_arguments']) { + $element[$element['#columns'][1]] = array( + '#type' => ($settings['arguments']['rows'] == 1 ? 'textfield' : 'textarea'), + '#default_value' => isset($element['#value'][$element['#columns'][1]]) ? $element['#value'][$element['#columns'][1]] : '', + '#title' => isset($settings['arguments']['label']) ? + str_replace('!field_label', $instance['label'], check_plain($settings['arguments']['label'])) : + $instance['label'] . ' ' . t('arguments'), + '#rows' => $settings['arguments']['rows'], + '#language' => $language, + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + //'#required' => $element['#required'], + //'#description' => isset($element['#description']) ? $element['#description'] : NULL, + ); + } + + return $element; +} + +/** + * Process callback for a viewreference_autocomplete element. + * + * @see viewreference_element_info(). + */ +function viewreference_autocomplete_process($element, $form_state, $form) { + $field_name = $element['#parents'][0]; + $language = $element['#parents'][1]; + $field = $form_state['field'][$field_name]; + $instance = $field[$language]['instance']; + $bundle = $instance['bundle']; + $settings = $field[$language]['field']['settings']; + + $element[$element['#columns'][0]] = array( + '#type' => 'textfield', + '#default_value' => isset($element['#value'][$element['#columns'][0]]) ? $element['#value'][$element['#columns'][0]] : '', + '#autocomplete_path' => 'viewreference/autocomplete/'. $bundle . '/' . $field_name, + '#element_validate' => array('viewreference_autocomplete_validate'), + '#field_name' => $field_name, + '#language' => $language, + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#title' => $instance['label'], + '#required' => $element['#required'], + '#description' => isset($element['#description']) ? $element['#description'] : '', + ); + + if ($settings['arguments']['dsv_arguments'] || $settings['arguments']['php_arguments']) { + $element[$element['#columns'][1]] = array( + '#type' => ($settings['arguments']['rows'] == 1 ? 'textfield' : 'textarea'), + '#default_value' => isset($element['#value'][$element['#columns'][1]]) ? $element['#value'][$element['#columns'][1]] : '', + '#title' => $instance['label'] . ' ' . t('arguments'), + '#rows' => $settings['arguments']['rows'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#required' => $element['#required'], + '#description' => isset($element['#description']) ? $element['#description'] : '', + ); + } + + return $element; +} + +/* + + Views field exposure code from D6. Don't know where to put this.' + Used to be in viewreference_field_settings() when $op was 'views data'. + + $data = content_views_field_views_data($field); + $db_info = content_database_info($field); + $table_alias = content_views_tablename($field); + // Swap the filter handler to the 'in' operator. + $data[$table_alias][$field['field_name'] . '_view_id']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; + return $data; + +*/ \ No newline at end of file diff --git a/sites/all/modules/views/js/views-admin.js b/sites/all/modules/views/js/views-admin.js index e945429994ddcc852ebc0928bfa18e6a40203fbe..2b4ccf339411fadd6298c91a39ed14f71c86f607 100644 --- a/sites/all/modules/views/js/views-admin.js +++ b/sites/all/modules/views/js/views-admin.js @@ -255,10 +255,11 @@ Drupal.behaviors.viewsUiRenderAddViewButton.attach = function (context, settings // away from the item. We use mouseleave instead of mouseout because // the user is going to trigger mouseout when she moves from the trigger // link to the sub menu items. - // We use the live binder because the open class on this item will be + // + // We use the 'li.add' selector because the open class on this item will be // toggled on and off and we want the handler to take effect in the cases // that the class is present, but not when it isn't. - $('li.add', $menu).live('mouseleave', function (event) { + $menu.delegate('li.add', 'mouseleave', function (event) { var $this = $(this); var $trigger = $this.children('a[href="#"]'); if ($this.children('.action-list').is(':visible')) { diff --git a/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc index ba1a0e746875040013468803b4d2613708ac1903..435db0ddff84be02ffc55ddc6277a67cd5d369f7 100644 --- a/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc +++ b/sites/all/modules/views/modules/taxonomy/views_plugin_argument_validate_taxonomy_term.inc @@ -96,7 +96,6 @@ class views_plugin_argument_validate_taxonomy_term extends views_plugin_argument $query = db_select('taxonomy_term_data', 'td'); $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid'); $query->fields('td'); - $query->fields('tv', array('machine_name')); $query->condition('td.tid', $argument); $query->addTag('term_access'); $term = $query->execute()->fetchObject(); @@ -105,7 +104,7 @@ class views_plugin_argument_validate_taxonomy_term extends views_plugin_argument } $term = taxonomy_term_load($term->tid); $this->argument->validated_title = check_plain(entity_label('taxonomy_term', $term)); - return empty($vocabularies) || !empty($vocabularies[$term->machine_name]); + return empty($vocabularies) || !empty($vocabularies[$term->vocabulary_machine_name]); case 'tids': // An empty argument is not a term so doesn't pass. diff --git a/sites/all/modules/views/plugins/views_plugin_display_page.inc b/sites/all/modules/views/plugins/views_plugin_display_page.inc index 6ab95b710e906eab7895a6bcc5885fb4e4745cd9..7ca4bf7fe9057887750e1a09026c23833c751a8a 100644 --- a/sites/all/modules/views/plugins/views_plugin_display_page.inc +++ b/sites/all/modules/views/plugins/views_plugin_display_page.inc @@ -30,6 +30,7 @@ class views_plugin_display_page extends views_plugin_display { 'weight' => array('default' => 0), 'name' => array('default' => variable_get('menu_default_node_menu', 'navigation')), 'context' => array('default' => ''), + 'context_only_inline' => array('default' => FALSE), ), ); $options['tab_options'] = array( @@ -153,7 +154,7 @@ class views_plugin_display_page extends views_plugin_display { // Add context for contextual links. // @see menu_contextual_links() if (!empty($menu['context'])) { - $items[$path]['context'] = MENU_CONTEXT_INLINE; + $items[$path]['context'] = !empty($menu['context_only_inline']) ? MENU_CONTEXT_INLINE : (MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE); } // If this is a 'default' tab, check to see if we have to create teh @@ -386,12 +387,23 @@ class views_plugin_display_page extends views_plugin_display { ); $form['menu']['context'] = array( '#title' => t('Context'), - '#suffix' => '</div>', '#type' => 'checkbox', '#default_value' => !empty($menu['context']), '#description' => t('Displays the link in contextual links'), '#dependency' => array('radio:menu[type]' => array('tab')), ); + $form['menu']['context_only_inline'] = array( + '#title' => t('Hide menu tab'), + '#suffix' => '</div>', + '#type' => 'checkbox', + '#default_value' => !empty($menu['context_only_inline']), + '#description' => t('Only display menu item entry in contextual links. Menu tab should not be displayed.'), + '#dependency' => array( + 'radio:menu[type]' => array('tab'), + 'edit-menu-context' => array(1), + ), + '#dependency_count' => 2, + ); break; case 'tab_options': $form['#title'] .= t('Default tab options'); diff --git a/sites/all/modules/views/tests/views_test.info b/sites/all/modules/views/tests/views_test.info index 444ccc9c9db55ae4111330d8475f01e399a95d53..23bd884fdd4f6730c25dfa3d25debbf5aec037fa 100644 --- a/sites/all/modules/views/tests/views_test.info +++ b/sites/all/modules/views/tests/views_test.info @@ -5,9 +5,9 @@ core = 7.x dependencies[] = views hidden = TRUE -; Information added by drupal.org packaging script on 2013-03-20 -version = "7.x-3.6" +; Information added by drupal.org packaging script on 2013-04-09 +version = "7.x-3.7" core = "7.x" project = "views" -datestamp = "1363810217" +datestamp = "1365499236" diff --git a/sites/all/modules/views/views.api.php b/sites/all/modules/views/views.api.php index 33690e2fb705db1ce1b577549d80133fc1ca40f9..ba9b326f9601172fbbced7f6565be036ee675080 100644 --- a/sites/all/modules/views/views.api.php +++ b/sites/all/modules/views/views.api.php @@ -482,13 +482,15 @@ function hook_views_data_alter(&$data) { $data['users']['example_field'] = array( 'title' => t('Example field'), 'help' => t('Some example content that references a user'), - 'handler' => 'hook_handlers_field_example_field', + 'field' => array( + 'handler' => 'modulename_handler_field_example_field', + ), ); // This example changes the handler of the node title field. // In this handler you could do stuff, like preview of the node when clicking // the node title. - $data['node']['title']['handler'] = 'modulename_handlers_field_node_title'; + $data['node']['title']['field']['handler'] = 'modulename_handler_field_node_title'; // This example adds a relationship to table {foo}, so that 'foo' views can // add this table using a relationship. Because we don't want to write over diff --git a/sites/all/modules/views/views.info b/sites/all/modules/views/views.info index ceb1aeed14f64c7b14dcfa6c967a5a517a3f5233..50d814829ac0b5d2b6ff9ab96f6913835442b4b8 100644 --- a/sites/all/modules/views/views.info +++ b/sites/all/modules/views/views.info @@ -312,9 +312,9 @@ files[] = tests/views_cache.test files[] = tests/views_view.test files[] = tests/views_ui.test -; Information added by drupal.org packaging script on 2013-03-20 -version = "7.x-3.6" +; Information added by drupal.org packaging script on 2013-04-09 +version = "7.x-3.7" core = "7.x" project = "views" -datestamp = "1363810217" +datestamp = "1365499236" diff --git a/sites/all/modules/views/views_ui.info b/sites/all/modules/views/views_ui.info index b746aa43ae0729caf8672a4033f0e889ad5473e2..95ea014918c8f7f514b09984186194dac0d4185d 100644 --- a/sites/all/modules/views/views_ui.info +++ b/sites/all/modules/views/views_ui.info @@ -7,9 +7,9 @@ dependencies[] = views files[] = views_ui.module files[] = plugins/views_wizard/views_ui_base_views_wizard.class.php -; Information added by drupal.org packaging script on 2013-03-20 -version = "7.x-3.6" +; Information added by drupal.org packaging script on 2013-04-09 +version = "7.x-3.7" core = "7.x" project = "views" -datestamp = "1363810217" +datestamp = "1365499236" diff --git a/sites/all/settings.php.sample b/sites/all/settings.php.sample new file mode 100644 index 0000000000000000000000000000000000000000..67cc86f2cc682250025a03376d1725dd5caf7ea3 --- /dev/null +++ b/sites/all/settings.php.sample @@ -0,0 +1,54 @@ +<?php + +// Enable or disable maintenance mode. +// $conf['maintenance_mode'] = 0; + +// Tell drupal its ok to let varnish cache pages. +$conf['page_cache_invoke_hooks'] = FALSE; + +$conf['unl_clean_file_url'] = TRUE; + +// Enable page caching with a 1 day lifetime. +$conf['cache'] = true; +$conf['page_cache_maximum_age'] = 86400; +$conf['preprocess_css'] = 1; +$conf['preprocess_js'] = 1; + +// If the UNL CAS cookie is set, we don't want pages cached. +if (isset($_COOKIE['unl_sso']) && request_path() != 'admin/config/development/performance') { + $conf['cache'] = FALSE; +} + +// Configure the memcache cache backend. +$conf['cache_backends'][] = 'sites/all/modules/memcache/memcache.inc'; +$conf['memcache_servers'] = array('127.0.0.1:11211' => 'default'); +$conf['memcache_key_prefix'] = 'host.example.com/path' . conf_path(); + +// Configure the varnish cache backend. +$conf['cache_backends'][] = 'sites/all/modules/varnish/varnish.cache.inc'; +$conf['varnish_control_terminal'] = '127.0.0.1:6082'; +$conf['varnish_control_key'] = 'password'; +$conf['varnish_cache_clear'] = 1; + +// Set the default cache backend. +$conf['cache_default_class'] = 'MemCacheDrupal'; +#$conf['cache_class_cache_page'] = 'VarnishCache'; + +$databases = array ( + 'default' => + array ( + 'default' => + array ( + 'driver' => 'mysql', + 'database' => 'drupal', + 'username' => 'drupal', + 'password' => 'password', + 'host' => 'localhost', + 'port' => '', + 'prefix' => 'drupal_', + ), + ), +); + +$conf['reverse_proxy'] = TRUE; +$conf['reverse_proxy_addresses'] = array('127.0.0.1'); diff --git a/sites/all/themes/unl_wdn/color/preview.html b/sites/all/themes/unl_wdn/color/preview.html index 3d1cf3f38c9d74ab4f049c774ae0ab28fc9dfb2f..9ad8510925fbe79a9f01911d0416ba72546a4cf8 100644 --- a/sites/all/themes/unl_wdn/color/preview.html +++ b/sites/all/themes/unl_wdn/color/preview.html @@ -36,8 +36,8 @@ </div> <div id="preview-footer-wrapper"> - <p>Copyright University of Nebraska–Lincoln | Lincoln, NE 68588 | 402-472-7211 | <a>About UNL</a></p> + <p>Copyright University of Nebraska–Lincoln | Lincoln, NE 68588 | 402-472-7211 | <a>About UNL</a></p> <p>UNL is an equal opportunity employer with a comprehensive plan for diversity.</p> </div> -</div> \ No newline at end of file +</div> diff --git a/sites/all/themes/unl_wdn/lib/.pear2registry b/sites/all/themes/unl_wdn/lib/.pear2registry index b4b979dfe7fbcdd47f323cc982d05403dca9ca01..4bfb2b8bddf4d41abd319514a159887f7adc7970 100644 Binary files a/sites/all/themes/unl_wdn/lib/.pear2registry and b/sites/all/themes/unl_wdn/lib/.pear2registry differ diff --git a/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.8.0-info.xml b/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.9.0-info.xml similarity index 92% rename from sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.8.0-info.xml rename to sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.9.0-info.xml index 817ae5a5557fd79d71cf031af5e55af50573b908..894b761f0972bda69fd35554ebe3513999cb2108 100644 --- a/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.8.0-info.xml +++ b/sites/all/themes/unl_wdn/lib/.xmlregistry/packages/pear.unl.edu/UNL_DWT/0.9.0-info.xml @@ -13,10 +13,10 @@ This package generates php class files (objects) from Dreamweaver template files <email>brett.bieber@gmail.com</email> <active>yes</active> </lead> - <date>2012-10-02</date> - <time>11:50:32</time> + <date>2013-05-24</date> + <time>10:02:57</time> <version> - <release>0.8.0</release> + <release>0.9.0</release> <api>0.7.1</api> </version> <stability> @@ -24,22 +24,21 @@ This package generates php class files (objects) from Dreamweaver template files <api>beta</api> </stability> <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD</license> - <notes>Feature Release: - -* Dreamweaver templates using params are now properly supported -* Minor API changes -** Add UNL_DWT::getTemplateFile() which is used during the rendering process + <notes>Feature Release +* Add support for immedaitely rendering a scanned DWT [saltybeagle] +Bug Fixes + * Prevent greedy matching of template regions [spam38] </notes> <contents> <dir name="/"> - <file role="php" name="php/UNL/DWT/Scanner.php" md5sum="042fb529bbc104b3eb742e8946e0961e"/> + <file role="php" name="php/UNL/DWT/Scanner.php" md5sum="276e82e5db587c9c18a100e1e877082e"/> <file role="php" name="php/UNL/DWT/Region.php" md5sum="858136d43bf29868dca876783e51d196"/> <file role="php" name="php/UNL/DWT/Generator.php" md5sum="a3b933a0d7f8d81f72836bb2c5fb6914"/> <file role="php" name="php/UNL/DWT/Exception.php" md5sum="5b99b44fbfde7349c6b9e6d9be78e9dc"/> <file role="php" name="php/UNL/DWT/createTemplates.php" md5sum="9089565d275b52e0cd65c52edd50ef18"/> - <file role="php" name="php/UNL/DWT.php" md5sum="0889c13bcecd0ebf5e517367345772f9"/> - <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/scanner_example.php" md5sum="f0807792c3c0c4a0524f9186e69e0be7"/> + <file role="php" name="php/UNL/DWT.php" md5sum="ca9d707c266ad9150e39d1a9a60c5643"/> + <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/scanner_example.php" md5sum="2d16f0e62c4227aa28108bf78d74156a"/> <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/basic/Template_style1.tpl" md5sum="b524ef4684be7dba47ed8c245577347a"/> <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/basic/Template_style1.php" md5sum="096998b112a1e27bddc6c171380d590e"/> <file role="doc" name="doc/pear.unl.edu/UNL_DWT/examples/basic/template_style1.dwt" md5sum="0d5a4f5ca86e9c2a3c0050f39acbb034"/> diff --git a/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php b/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php index 05d117dd0d28ebdaf0a65867d8bdf24e6213fa92..00184321118c0efda8e8b714c02ca742b04efb3c 100644 --- a/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php +++ b/sites/all/themes/unl_wdn/lib/docs/pear.unl.edu/UNL_DWT/examples/scanner_example.php @@ -1,6 +1,8 @@ <?php set_include_path(dirname(__DIR__).'/../src'); +error_reporting(E_ALL); +ini_set('display_errors', true); require_once 'UNL/DWT/Scanner.php'; @@ -8,5 +10,10 @@ $file = file_get_contents(dirname(__FILE__).'/basic/'.'template_style1.dwt'); $scanned = new UNL_DWT_Scanner($file); -echo $scanned->leftnav; -echo $scanned->content; +// Modify the scanned content +$scanned->content .= '<h3>Scanned content from the left nav:</h3>'; + +// Also, access the content that was scanned in +$scanned->content .= '<pre>'.$scanned->leftnav.'</pre>'; + +echo $scanned; diff --git a/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php b/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php index 3e7bc3479847741f476ad94dddde4ab6ac573f38..696e79ad1244dde3c1bfcf2845b88575a22195c4 100644 --- a/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php +++ b/sites/all/themes/unl_wdn/lib/php/UNL/DWT.php @@ -149,17 +149,23 @@ class UNL_DWT foreach ($regions as $region => $value) { /* Replace the region with the replacement text */ + $startMarker = $this->getRegionBeginMarker(self::TEMPLATE_TOKEN, $region); + $endMarker = $this->getRegionEndMarker(self::TEMPLATE_TOKEN); $p = str_replace( - self::strBetween($this->getRegionBeginMarker(self::TEMPLATE_TOKEN, $region), - $this->getRegionEndMarker(self::TEMPLATE_TOKEN), $p), - $value, $p, $count + self::strBetween($startMarker, $endMarker, $p, true), + $startMarker . $value . $endMarker, + $p, + $count ); if (!$count) { + $startMarker = $this->getRegionBeginMarker(self::INSTANCE_TOKEN, $region); + $endMarker = $this->getRegionEndMarker(self::INSTANCE_TOKEN); $p = str_replace( - self::strBetween($this->getRegionBeginMarker(self::INSTANCE_TOKEN, $region), - $this->getRegionEndMarker(self::INSTANCE_TOKEN), $p), - $value, $p, $count + self::strBetween($startMarker, $endMarker, $p, true), + $startMarker . $value . $endMarker, + $p, + $count ); } @@ -299,16 +305,16 @@ class UNL_DWT * * @return string */ - static function strBetween($start, $end, $p) + static function strBetween($start, $end, $p, $inclusive = false) { if (!empty($start) && strpos($p, $start) !== false) { - $p = substr($p, strpos($p, $start)+strlen($start)); + $p = substr($p, strpos($p, $start)+($inclusive ? 0 : strlen($start))); } else { return ''; } if (strpos($p, $end) !==false) { - $p = substr($p, 0, strpos($p, $end)); + $p = substr($p, 0, strpos($p, $end)+($inclusive ? strlen($end) : 0)); } else { return ''; } diff --git a/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php b/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php index c6e79eadd2fb111ad0aee938573aefced20b61be..e4a2a29bfa2816146e046a5e1a6861d7e0e8a722 100644 --- a/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php +++ b/sites/all/themes/unl_wdn/lib/php/UNL/DWT/Scanner.php @@ -1,6 +1,6 @@ <?php /** - * Handles scanning a dwt file for regions. + * Handles scanning a dwt file for regions and rendering. * * PHP version 5 * @@ -12,6 +12,7 @@ * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License * @link http://pear.unl.edu/package/UNL_DWT */ +require_once 'UNL/DWT.php'; require_once 'UNL/DWT/Region.php'; /** @@ -23,7 +24,7 @@ require_once 'UNL/DWT/Region.php'; * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License * @link http://pear.unl.edu/package/UNL_DWT */ -class UNL_DWT_Scanner +class UNL_DWT_Scanner extends UNL_DWT { protected $_regions; @@ -34,9 +35,20 @@ class UNL_DWT_Scanner */ function __construct($dwt) { + $this->__template = $dwt; $this->scanRegions($dwt); } + /** + * Return the template markup + * + * @return string + */ + function getTemplateFile() + { + return $this->__template; + } + function scanRegions($dwt) { $this->_regions[] = array(); @@ -133,4 +145,14 @@ class UNL_DWT_Scanner return null; } + /** + * Allow directly rendering + * + * @return string + */ + function __toString() + { + return $this->toHtml(); + } + } diff --git a/sites/all/themes/unl_wdn/template.php b/sites/all/themes/unl_wdn/template.php index 39d6f1c095837ba5d6f72fde2e71128025a6889e..166c0efec2000876ec8526abda6d5e657aae26d8 100644 --- a/sites/all/themes/unl_wdn/template.php +++ b/sites/all/themes/unl_wdn/template.php @@ -202,7 +202,7 @@ function unl_wdn_preprocess_html(&$vars, $hook) { unset($vars['head_title_array']['title']); } if (variable_get('site_name') != 'University of Nebraska–Lincoln') { - $vars['head_title_array'] = array_merge($vars['head_title_array'], array('UNL' => 'University of Nebraska–Lincoln')); + $vars['head_title_array'] = array_merge($vars['head_title_array'], array('UNL' => 'University of Nebraska–Lincoln')); } $vars['head_title'] = implode(' | ', $vars['head_title_array']); } @@ -285,20 +285,11 @@ function unl_wdn_preprocess_username(&$vars) { */ function unl_wdn_username_alter(&$name, $account) { if ($account->uid) { - // Drupal does not support "display names" so convert the user name (jdoe2 to Jane Doe) using UNL Directory service. - $context = stream_context_create(array( - 'http' => array('timeout' => 1) - )); - if (function_exists('unl_url_get_contents')) { - $result = json_decode(unl_url_get_contents('http://directory.unl.edu/service.php?format=json&uid='.$name, $context)); - } - else { - $result = json_decode(file_get_contents('http://directory.unl.edu/service.php?format=json&uid='.$name, 0, $context)); - } - if (!empty($result) && $result->sn) { - $zero = '0'; - $firstname = ($result->eduPersonNickname ? $result->eduPersonNickname->$zero : $result->givenName->$zero); - $name = $firstname . ' ' . $result->sn->$zero; + // If the CAS module is enabled, we should have their full name. + $user = user_load_by_name($name); + if ($user && isset($user->data['unl']['fullName'])) { + $name = $user->data['unl']['fullName']; + return; } } } diff --git a/vendor/Twig b/vendor/Twig new file mode 160000 index 0000000000000000000000000000000000000000..a56f7405be86b900405d09b00fac2d73b6bdf85f --- /dev/null +++ b/vendor/Twig @@ -0,0 +1 @@ +Subproject commit a56f7405be86b900405d09b00fac2d73b6bdf85f diff --git a/vendor/WDN-TinyMCE b/vendor/WDN-TinyMCE index d2e8c2533e64ba71f2c5feb4a39e4c2490fd01aa..7fa4ac9c50f554b8e2b542160ca16ee3b4537f00 160000 --- a/vendor/WDN-TinyMCE +++ b/vendor/WDN-TinyMCE @@ -1 +1 @@ -Subproject commit d2e8c2533e64ba71f2c5feb4a39e4c2490fd01aa +Subproject commit 7fa4ac9c50f554b8e2b542160ca16ee3b4537f00