Browse Source

non security modules update

Bachir Soussi Chiadmi 9 years ago
parent
commit
37fbabab56
100 changed files with 5429 additions and 1846 deletions
  1. 57 0
      sites/all/modules/contrib/admin/date_popup_authored/.travis.yml
  2. 31 0
      sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.markdown
  3. 0 30
      sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.txt
  4. 78 0
      sites/all/modules/contrib/admin/date_popup_authored/README.markdown
  5. 0 76
      sites/all/modules/contrib/admin/date_popup_authored/README.txt
  6. 5 4
      sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.info
  7. 17 0
      sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.install
  8. 68 27
      sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.module
  9. 77 1
      sites/all/modules/contrib/admin/date_popup_authored/tests/date_popup_authored_format.test
  10. 120 27
      sites/all/modules/contrib/admin/features/features.admin.inc
  11. 56 0
      sites/all/modules/contrib/admin/features/features.api.php
  12. 13 0
      sites/all/modules/contrib/admin/features/features.css
  13. 104 16
      sites/all/modules/contrib/admin/features/features.drush.inc
  14. 12 15
      sites/all/modules/contrib/admin/features/features.export.inc
  15. 3 3
      sites/all/modules/contrib/admin/features/features.info
  16. 11 1
      sites/all/modules/contrib/admin/features/features.install
  17. 0 28
      sites/all/modules/contrib/admin/features/features.js
  18. 25 5
      sites/all/modules/contrib/admin/features/features.module
  19. 37 3
      sites/all/modules/contrib/admin/features/includes/features.field.inc
  20. 20 11
      sites/all/modules/contrib/admin/features/includes/features.image.inc
  21. 1 0
      sites/all/modules/contrib/admin/features/includes/features.locale.inc
  22. 31 18
      sites/all/modules/contrib/admin/features/includes/features.menu.inc
  23. 8 4
      sites/all/modules/contrib/admin/features/includes/features.node.inc
  24. 2 2
      sites/all/modules/contrib/admin/features/includes/features.user.inc
  25. 0 8
      sites/all/modules/contrib/admin/features/tests/features_test/features_test.features.inc
  26. 3 3
      sites/all/modules/contrib/admin/features/tests/features_test/features_test.info
  27. 1 1
      sites/all/modules/contrib/admin/features/theme/features-admin-components.tpl.php
  28. 31 0
      sites/all/modules/contrib/admin/features/theme/theme.inc
  29. 3 3
      sites/all/modules/contrib/admin/filter_perms/filter_perms.info
  30. 16 13
      sites/all/modules/contrib/admin/google_analytics/README.txt
  31. 39 5
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc
  32. 1 1
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js
  33. 183 0
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js
  34. 4 4
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.info
  35. 126 23
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.install
  36. 103 31
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.js
  37. 174 120
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.module
  38. 411 132
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.test
  39. 119 0
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js
  40. 2 1
      sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc
  41. 173 41
      sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt
  42. 107 0
      sites/all/modules/contrib/admin/module_filter/README.txt
  43. 23 0
      sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css
  44. 17 1
      sites/all/modules/contrib/admin/module_filter/css/module_filter.css
  45. 54 0
      sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css
  46. 221 95
      sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css
  47. 47 0
      sites/all/modules/contrib/admin/module_filter/css/modules.css
  48. 18 0
      sites/all/modules/contrib/admin/module_filter/css/update_status.css
  49. 36 21
      sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js
  50. 245 94
      sites/all/modules/contrib/admin/module_filter/js/module_filter.js
  51. 508 227
      sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js
  52. 176 0
      sites/all/modules/contrib/admin/module_filter/js/modules.js
  53. 67 0
      sites/all/modules/contrib/admin/module_filter/js/permissions.js
  54. 117 0
      sites/all/modules/contrib/admin/module_filter/js/update_status.js
  55. 62 7
      sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc
  56. 4 3
      sites/all/modules/contrib/admin/module_filter/module_filter.info
  57. 26 1
      sites/all/modules/contrib/admin/module_filter/module_filter.install
  58. 172 80
      sites/all/modules/contrib/admin/module_filter/module_filter.module
  59. 46 0
      sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc
  60. 172 57
      sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc
  61. 333 268
      sites/all/modules/contrib/admin/override_node_options/LICENSE.txt
  62. 4 3
      sites/all/modules/contrib/admin/override_node_options/override_node_options.info
  63. 22 0
      sites/all/modules/contrib/admin/override_node_options/override_node_options.install
  64. 14 4
      sites/all/modules/contrib/admin/override_node_options/override_node_options.module
  65. 2 3
      sites/all/modules/contrib/dev/devel/README.txt
  66. 3 3
      sites/all/modules/contrib/dev/devel/devel.admin.inc
  67. 29 31
      sites/all/modules/contrib/dev/devel/devel.drush.inc
  68. 3 3
      sites/all/modules/contrib/dev/devel/devel.info
  69. 29 2
      sites/all/modules/contrib/dev/devel/devel.install
  70. 5 5
      sites/all/modules/contrib/dev/devel/devel.js
  71. 4 1
      sites/all/modules/contrib/dev/devel/devel.mail.inc
  72. 8 6
      sites/all/modules/contrib/dev/devel/devel.module
  73. 8 11
      sites/all/modules/contrib/dev/devel/devel.pages.inc
  74. 2 1
      sites/all/modules/contrib/dev/devel/devel.test
  75. 47 11
      sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.drush.inc
  76. 75 65
      sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.inc
  77. 4 4
      sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info
  78. 76 17
      sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module
  79. 108 0
      sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test
  80. 1 1
      sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc
  81. 2 4
      sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc
  82. 2 6
      sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc
  83. 1 1
      sites/all/modules/contrib/dev/devel/devel_krumo_path.js
  84. 3 3
      sites/all/modules/contrib/dev/devel/devel_node_access.info
  85. 7 7
      sites/all/modules/contrib/dev/devel/devel_node_access.js
  86. 48 11
      sites/all/modules/contrib/dev/devel/devel_node_access.module
  87. 2 1
      sites/all/modules/contrib/dev/devel/krumo/class.krumo.php
  88. 22 0
      sites/all/modules/contrib/dev/devel/runtests.sh
  89. 22 6
      sites/all/modules/contrib/dev/libraries/CHANGELOG.txt
  90. 42 18
      sites/all/modules/contrib/dev/libraries/libraries.api.php
  91. 5 3
      sites/all/modules/contrib/dev/libraries/libraries.info
  92. 127 23
      sites/all/modules/contrib/dev/libraries/libraries.module
  93. 0 11
      sites/all/modules/contrib/dev/libraries/tests/example/example_1.css
  94. 0 11
      sites/all/modules/contrib/dev/libraries/tests/example/example_2.css
  95. 0 11
      sites/all/modules/contrib/dev/libraries/tests/example/example_3.css
  96. 0 11
      sites/all/modules/contrib/dev/libraries/tests/example/example_4.css
  97. 63 27
      sites/all/modules/contrib/dev/libraries/tests/libraries.test
  98. 9 8
      sites/all/modules/contrib/dev/libraries/tests/libraries/example/README.txt
  99. 12 0
      sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.css
  100. 2 2
      sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.js

+ 57 - 0
sites/all/modules/contrib/admin/date_popup_authored/.travis.yml

@@ -0,0 +1,57 @@
+language: php
+
+php:
+  - 5.3
+  - 5.4
+  - 5.5
+  - 5.6
+  - hhvm
+
+matrix:
+  fast_finish: true
+  allow_failures:
+    - php: hhvm
+
+mysql:
+  database: date_popup_authored_test
+  username: root
+  encoding: utf8
+
+before_install:
+  - sudo apt-get update > /dev/null
+
+install:
+  # install php packages required for running a web server from drush on php 5.3
+  - sudo apt-get install -y --force-yes php5-cgi php5-mysql
+
+  # add composer's global bin directory to the path
+  # see: https://github.com/drush-ops/drush#install---composer
+  - export PATH="$HOME/.composer/vendor/bin:$PATH"
+
+  # install drush globally
+  - composer global require drush/drush:6.*
+
+before_script:
+
+  # Sendmail support.
+  - if [[ "$TRAVIS_PHP_VERSION" != hhvm* ]]; then echo 'sendmail_path = /bin/true' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi
+  # hhvm ignores sendmail_path and hhvm.mail.sendmail_path settings presently...
+  - if [[ "$TRAVIS_PHP_VERSION" == hhvm* ]]; then sudo ln -s /bin/true /usr/local/bin/sendmail; fi
+
+   # navigate out of module directory to prevent blown stack by recursive module lookup
+  - cd ../..
+
+  # create new site, stubbing sendmail path with true to prevent delivery errors and manually resolving drush path
+  - mysql -e 'create database date_popup_authored_test'
+  - php ~/.composer/vendor/bin/drush.php --yes core-quick-drupal --profile=testing --no-server --db-url=mysql://root:@127.0.0.1/date_popup_authored_test --enable=simpletest date_popup_authored_test
+
+  # reference and enable travis_ci_drupal_module_example in build site
+  - ln -s $(readlink -e $(cd -)) date_popup_authored_test/drupal/sites/all/modules/date_popup_authored
+  - cd date_popup_authored_test/drupal
+  - drush --yes pm-enable date date_popup date_popup_authored
+
+  # start a web server on port 8080, run in the background; wait for initialization
+  - drush runserver 127.0.0.1:8080 &
+  - until netstat -an 2>/dev/null | grep '8080.*LISTEN'; do true; done
+
+script: drush test-run 'Date Popup Authored' --uri=http://127.0.0.1:8080

+ 31 - 0
sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.markdown

@@ -0,0 +1,31 @@
+# Date Popup Authored Changelog
+
+## 7.x-1.2
+
+- Travis CI support
+- [#2329773][4] by amagdy, Mark Trapp: Clean up variables when no longer in use
+- [#2051921][3] by pfrenssen: Warning: strtotime() expects parameter 1 to be string, array given in strtotime
+- [#2275593][2] by goodboy: Use date_format_short for default value.
+- [#1087616][1] by Mark Trapp: Date Popup Authored does not play nice when $form['authored']['#access'] is modified too late
+
+[1]: https://www.drupal.org/node/1087616
+[2]: https://www.drupal.org/node/2275593
+[3]: https://www.drupal.org/node/2051921
+[4]: https://www.drupal.org/node/2329773
+
+## 7.x-1.1
+
+- Drupal 7 support
+- [#1087616][6] by Mark Trapp: Post date resets on save if user can't administer nodes
+- [#1012288][5] by pillarsdotnet: Make Date Popup Authored work in PHP 5.2
+- [#995934][4] by Mark Trapp: Date Popup Authored needs tests
+- [#995060][3] by pillarsdotnet: Date Popup Authored assumes date is a DateObject, shouldn't
+- [#970622][2] by Mark Trapp: Saving a node with Date Popup Authored enabled will result in published time drift
+- [#970406][1] by Mark Trapp: Creating a new node with Date Popup Authored enabled results in a White Screen of Death
+
+[1]: https://www.drupal.org/node/970406
+[2]: https://www.drupal.org/node/970622
+[3]: https://www.drupal.org/node/995060
+[4]: https://www.drupal.org/node/995934
+[5]: https://www.drupal.org/node/1012288
+[6]: https://www.drupal.org/node/1087616

+ 0 - 30
sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.txt

@@ -1,30 +0,0 @@
-Date Popup Authored Module 7.x
-
-Date Popup Authored 7.x-1.1
----------------------------
-Stable release to coincide with stable release of Date.
-
-Date Popup Authored 7.x-1.1-beta2
--------------------------------
-#1087616 by Mark Trapp: Post date resets on save if user can't administer nodes
-
-Date Popup Authored 7.x-1.1-beta1
--------------------------------
-#1012288 by pillarsdotnet: Make Date Popup Authored work in PHP 5.2
-
-Date Popup Authored 7.x-1.1-alpha4
--------------------------------
-#995934 by Mark Trapp: Date Popup Authored needs tests
-#995060 by pillarsdotnet: Date Popup Authored assumes date is a DateObject, shouldn't
-
-Date Popup Authored 7.x-1.1-alpha3
-----------------------------------
-#970622 by Mark Trapp: Saving a node with Date Popup Authored enabled will result in published time drift
-
-Date Popup Authored 7.x-1.1-alpha2
-----------------------------------
-#970406 by Mark Trapp: Creating a new node with Date Popup Authored enabled results in a White Screen of Death
-
-Date Popup Authored 7.x-1.1-alpha1
-----------------------------------
-Initial unstable release of the Drupal 7 port.

+ 78 - 0
sites/all/modules/contrib/admin/date_popup_authored/README.markdown

@@ -0,0 +1,78 @@
+# Date Popup Authored
+
+[![Build Status](https://travis-ci.org/itafroma/drupal-date_popup_authored.svg?branch=7.x-1.x)](https://travis-ci.org/itafroma/drupal-date_popup_authored)
+
+## Introduction
+
+Date Popup Authored provides a jQuery UI datepicker for the "Authored on" date field found on node submission forms.
+
+For a full description of the module, visit the [project page][1] on Drupal.org.
+To submit bug reports and feature suggestions, or to track changes, please visit the [issue queue][2].
+
+[1]: https://drupal.org/project/date_popup_authored "Date Popup Authored project page"
+[2]: https://drupal.org/project/issues/date_popup_authored "Date Popup Authored issue tracker"
+
+## Requirements
+
+- Drupal 7
+- [Date][3] 2.0 or later
+- Date Popup, part of the Date module
+
+[3]: https://drupal.org/project/date "Date project page"
+
+## Installation and configuration
+
+Install as usual. See the [handbook page on installing contributed modules][4] for further information.
+
+You can change the behavior of the date-picker by going to the settings page for each content type.
+
+[4]: https://drupal.org/node/895232 "Installing modules (Drupal 7)"
+
+## Caveats
+
+Since Date Popup Authored allows you to choose a date format that's less specific than the default date format Drupal uses for the Authored on field, it will insert default data if you use a more simplified date format.
+
+For example, if the date format you've configured doesn't include a time, when the node is saved, the Authored on time will be set to 12:00AM. Similarly, if you don't include the ability to choose a month, the Authored on month will be set to January (i.e. month 1).
+
+So, if you care about the time a post is authored, make sure you allow the user to set it in the date format. See installation for more information.
+
+## Future development
+
+The functionality this module provides is being considered for core inclusion:
+
+- [#1835016: Polyfill date input type][1]
+- [#471942-30: Use Date Popup on 'Authored on' field][2]
+- [#504524: Extend Authored on field with jQuery UI Date Picker][3]
+
+Because of this, there will hopefully be no Drupal 8 version.
+
+[5]: https://www.drupal.org/node/1835016 "#1835016: Polyfill date input type"
+[6]: https://www.drupal.org/comment/6788664#comment-6788664 "#471942-30: Use Date Popup on 'Authored on' field"
+[7]: https://www.drupal.org/node/504524 "#504524: Extend Authored on field with jQuery UI Date Picker"
+
+## Contact
+
+The current maintainer is [Mark Trapp][5] ([Drupal.org profile][6]).
+
+[8]: http://marktrapp.com "Mark Trapp's website"
+[9]: https://drupal.org/u/mark-trapp "Mark Trapp's Drupal.org profile"
+
+## Acknowledgments
+
+Date Popup Authored was inspired by the hacks provided by [brice][7] and [Rob Loach][8] in the Date module issue, "[Use Date Popup on 'Authored on' field][9]." It contains additional fixes to account for problems found in their solution, new configuration options, Drupal 7 support, and a full test suite.
+
+[10]: https://drupal.org/user/446296 "brice's Drupal.org profile"
+[11]: https://drupal.org/u/robloach "Rob Loach's Drupal.org profile"
+[12]: https://drupal.org/node/471942 "Use Date Popup on 'Authored on' field"
+
+## More information
+
+- For additional documentation, see the [online Drupal handbook][10].
+- For a list of security announcements, see the [*Security advisories* page][11] (available as an RSS feed). This page also describes how to subscribe to these announcements via e-mail.
+- For information about the Drupal security process, or to find out how to report a potential security issue to the Drupal security team, see the [*Security team* page][12].
+- For information about the wide range of available support options, see the [*Support* page][13].
+
+[13]: https://drupal.org/handbook "Drupal Handbook"
+[14]: https://drupal.org/security "Drupal security advisories"
+[15]: https://drupal.org/security-team "Drupal security team"
+[16]: https://drupal.org/support] "Drupal support"

+ 0 - 76
sites/all/modules/contrib/admin/date_popup_authored/README.txt

@@ -1,76 +0,0 @@
-
-CONTENTS OF THIS FILE
----------------------
-
-* Introduction
-* Requirements
-* Installation
-* Acknowledgements
-* Contact
-* More Information
-
-INTRODUCTION
-------------
-
-Date Popup Authored provides a jQuery UI datepicker for the "Authored on"
-date field found on node submission forms.
-
-For a full description of the module, visit the project page:
-  http://drupal.org/project/date_popup_authored
-
-To submit bug reports and feature suggestions, or to track changes:
-  http://drupal.org/project/issues/date_popup_authored
-
-REQUIREMENTS
-------------
-
-- Drupal 7
-- Date [1] 7.x-2.0 or later
-- Date Popup, part of the Date module
-
-[1] http://drupal.org/project/date
-
-INSTALLATION
-------------
-
-Install as usual. See the handbook page on installing contributed modules [1]
-for further information.
-
-You can change the behavior of the datepicker by going to the settings page
-for each content type.
-
-[1] http://drupal.org/node/895232
-
-CONTACT
--------
-
-Current maintainer:
-- Mark Trapp (amorfati) - http://drupal.org/user/212019
-
-ACKNOWLEDGEMENTS
-----------------
-
-Date Popup Authored was inspired by the hacks provided by brice [1] and
-Rob Loach [2] in issue #471942 [3]. It contains additional fixes to account for
-problems found in their solution as well as new configuration options.
-
-[1] http://drupal.org/user/446296
-[2] http://drupal.org/user/61114
-[3] http://drupal.org/node/471942
-
-MORE INFORMATION
-----------------
-
-- For additional documentation, see the online Drupal handbook at
-  http://drupal.org/handbook.
-
-- For a list of security announcements, see the "Security announcements" page
-  at http://drupal.org/security (available as an RSS feed). This page also
-  describes how to subscribe to these announcements via e-mail.
-
-- For information about the Drupal security process, or to find out how to report
-  a potential security issue to the Drupal security team, see the "Security team"
-  page at http://drupal.org/security-team.
-
-- For information about the wide range of available support options, see the
-  "Support" page at http://drupal.org/support.

+ 5 - 4
sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.info

@@ -1,12 +1,13 @@
 name = "Date Popup Authored"
 description = "Provides a datepicker for the 'Authored on' field on node forms."
 core = 7.x
-files[] = date_popup_authored.test
+files[] = tests/date_popup_authored_format.test
 package = Date/Time
 dependencies[] = date_popup
-; Information added by drupal.org packaging script on 2012-03-31
-version = "7.x-1.1"
+
+; Information added by Drupal.org packaging script on 2014-12-22
+version = "7.x-1.2"
 core = "7.x"
 project = "date_popup_authored"
-datestamp = "1333178146"
+datestamp = "1419241381"
 

+ 17 - 0
sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.install

@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * Install, update, and uninstall functions for the Date Popup Authored module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function date_popup_authored_uninstall() {
+  // Delete the variables created by Date Popup Authored.
+  foreach (node_type_get_types() as $node_type) {
+    variable_del('date_popup_authored_enabled_' . $node_type->type);
+    variable_del('date_popup_authored_format_' . $node_type->type);
+    variable_del('date_popup_authored_year_range_' . $node_type->type);
+  }
+}

+ 68 - 27
sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.module

@@ -110,39 +110,80 @@ function date_popup_authored_form_node_type_form_alter(&$form, &$form_state, $fo
 }
 
 /**
- * Implements hook_form_alter().
+ * Implements hook_form_BASE_FORM_ID_alter() for node_form.
+ *
+ * Replaces default Authored on field with a datepicker on node submission forms.
  */
-function date_popup_authored_form_alter(&$form, $form_state, $form_id) {
-
-  // Replaces default Authored on field with a datepicker on node submission forms.
-  if (strstr($form_id, '_node_form') !== FALSE) {
-    // Date Popup Authored should modify the node submission form only if the
-    // user is allowed to modify the authoring information and it's enabled.
-    if (!($form['author']['#access'] && variable_get('date_popup_authored_enabled_' . $form['type']['#value'], 1))) {
-      return;
-    }
+function date_popup_authored_form_node_form_alter(&$form, $form_state, $form_id) {
+  // Date Popup Authored should modify the node submission form only if the
+  // user is allowed to modify the authoring information and it's enabled.
+  if (!($form['author']['#access'] && variable_get('date_popup_authored_enabled_' . $form['type']['#value'], 1))) {
+    return;
+  }
 
-    $form['author']['date']['#type'] = 'date_popup';
+  $form['author']['date']['#type'] = 'date_popup';
 
-    // If there is a date already available, rewrite it to conform to
-    // Date Popup's expected format.
-    if (!empty($form['author']['date']['#default_value'])) {
-      $date = new DateObject($form['author']['date']['#default_value'], NULL, 'Y-m-d H:i:s O');
-      $form['author']['date']['#default_value'] = $date->format('Y-m-d H:i:s');
-    }
+  // If there is a date already available, rewrite it to conform to
+  // Date Popup's expected format.
+  if (!empty($form['author']['date']['#default_value'])) {
+    $date = new DateObject($form['author']['date']['#default_value'], NULL, 'Y-m-d H:i:s O');
+    $form['author']['date']['#default_value'] = $date->format('Y-m-d H:i:s');
+  }
+
+  // Set options specific to date_popup.
+  $year_range = variable_get('date_popup_authored_year_range_' . $form['type']['#value'], 3);
+  $form['author']['date']['#date_year_range'] = '-' . $year_range . ':+' . $year_range;
+  $form['author']['date']['#date_format'] = variable_get('date_popup_authored_format_' . $form['type']['#value'], variable_get('date_format_short', 'm/d/Y - H:i'));
+
+  // Unset options that are not relevant to date_popup
+  unset($form['author']['date']['#maxlength']);
+  unset($form['author']['date']['#description']);
+
+  // Add an additional validate handler after date_popup builds the element.
+  $form['author']['date']['#after_build'][] = 'date_popup_authored_element_after_build';
+
+  // We need to modify date popup's data during submit
+  // @see http://drupal.org/node/847854
+  $form['#submit'][] = 'date_popup_authored_node_form_submit';
+}
 
-    // Set options specific to date_popup.
-    $year_range = variable_get('date_popup_authored_year_range_' . $form['type']['#value'], 3);
-    $form['author']['date']['#date_year_range'] = '-' . $year_range . ':+' . $year_range;
-    $form['author']['date']['#date_format'] = variable_get('date_popup_authored_format_' . $form['type']['#value'], 'm/d/Y - H:i');
+/**
+ * Implements hook_node_type_delete().
+ */
+function date_popup_authored_node_type_delete($info) {
+  // Delete format configuration when node types are deleted.
+  variable_del('date_popup_authored_enabled_' . $info->type);
+  variable_del('date_popup_authored_format_' . $info->type);
+  variable_del('date_popup_authored_year_range_' . $info->type);
+}
+
+/**
+ * Form after build handler for the date popup element.
+ */
+function date_popup_authored_element_after_build($element, &$form_state) {
+  // Add a validate handler after the one that is added by date_popup.
+  $element['#element_validate'][] = 'date_popup_authored_element_validate';
+  return $element;
+}
 
-    // Unset options that are not relevant to date_popup
-    unset($form['author']['date']['#maxlength']);
-    unset($form['author']['date']['#description']);
+/**
+ * Validate handler for the date popup element.
+ */
+function date_popup_authored_element_validate($element, &$form_state) {
+  if (date_hidden_element($element) || is_string($element['#value'])) {
+    return;
+  }
 
-    // We need to modify date popup's data during submit
-    // @see http://drupal.org/node/847854
-    $form['#submit'][] = 'date_popup_authored_node_form_submit';
+  // If an error occurred in the validation of the date popup field the date
+  // cannot be correctly rendered as a string. In this case clear the date value
+  // to avoid subsequent errors when the node is validated.
+  // @see date_popup_validate()
+  // @see node_validate()
+  $input_exists = NULL;
+  $input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
+  $date = date_popup_input_date($element, $input);
+  if (is_object($date) && !empty($date->errors)) {
+    $form_state['values']['date'] = NULL;
   }
 }
 

+ 77 - 1
sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.test → sites/all/modules/contrib/admin/date_popup_authored/tests/date_popup_authored_format.test

@@ -87,7 +87,7 @@ class DatePopupAuthoredTestCase extends PageEditTestCase {
     debug($node_date->format($time_format), 'Node time');
 
     // Extract the date and time parts as seen on the node submission form
-    $default_format = variable_get('date_popup_authored_format_page', 'm/d/Y - H:i');
+    $default_format = variable_get('date_popup_authored_format_page', variable_get('date_format_short', 'm/d/Y - H:i'));
     variable_set('date_popup_authored_format_page', $format);
 
     $this->drupalGet('node/' . $node->nid . '/edit');
@@ -108,4 +108,80 @@ class DatePopupAuthoredTestCase extends PageEditTestCase {
     // Reset format back to default
     variable_set('date_popup_authored_format_page', $format);
   }
+
+  /**
+   * Tests form field validation.
+   */
+  function testFieldValidation() {
+    // Define some test cases.
+    $test_cases = array(
+      array(
+        'description' => 'a valid date field and a missing time field',
+        'date' => '02/07/2014',
+        'time' => '',
+        'valid' => FALSE,
+      ),
+      array(
+        'description' => 'a valid date field and a valid time field',
+        'date' => '02/07/2014',
+        'time' => '12:00',
+        'valid' => TRUE,
+      ),
+    );
+
+    // Log in as administrator.
+    $this->drupalLogin($this->admin_user);
+
+    // Test the test cases.
+    foreach ($test_cases as $test_case) {
+      $edit = array(
+        'title' => $this->randomString(),
+        'date[date]' => $test_case['date'],
+        'date[time]' => $test_case['time'],
+      );
+      $this->drupalPost('node/add/page', $edit, t('Save'));
+
+      $error_messages = $this->xpath('//div[contains(@class, "error")]');
+
+      $message = format_string('When submitting a node with @description the form validation correctly @result.', array(
+        '@description' => $test_case['description'],
+        '@result' => $test_case['valid'] ? 'succeeds' : 'fails',
+      ));
+      $this->assertEqual(empty($error_messages), $test_case['valid'], $message);
+    }
+  }
+
+  /**
+   * Tests variable cleanup after a content type is removed.
+   */
+  function testVariableCleanupAfterNodeTypeRemoval() {
+    $node_type_name = strtolower($this->randomName(8) . '_test');
+    $node_type = $this->drupalCreateContentType(array('name' => $node_type_name, 'type' => $node_type_name));
+
+    variable_set('date_popup_authored_enabled_' . $node_type_name, 'foo');
+    variable_set('date_popup_authored_format_' . $node_type_name, 'foo');
+    variable_set('date_popup_authored_year_range_' . $node_type_name, 'foo');
+
+    node_type_delete($node_type_name);
+
+    $this->assertNull(variable_get('date_popup_authored_enabled_' . $node_type_name));
+    $this->assertNull(variable_get('date_popup_authored_format_' . $node_type_name));
+    $this->assertNull(variable_get('date_popup_authored_year_range_' . $node_type_name));
+  }
+
+  /**
+   * Tests variable cleanup after Date Popup Authored is uninstalled.
+   */
+  function testVariableCleanupAfterUninstall() {
+    variable_set('date_popup_authored_enabled_page', 'foo');
+    variable_set('date_popup_authored_format_page', 'foo');
+    variable_set('date_popup_authored_year_range_page', 'foo');
+
+    module_disable(array('date_popup_authored'));
+    drupal_uninstall_modules(array('date_popup_authored'));
+
+    $this->assertNull(variable_get('date_popup_authored_enabled_page'));
+    $this->assertNull(variable_get('date_popup_authored_format_page'));
+    $this->assertNull(variable_get('date_popup_authored_year_range_page'));
+  }
 }

+ 120 - 27
sites/all/modules/contrib/admin/features/features.admin.inc

@@ -19,16 +19,39 @@ function features_settings_form($form, $form_state) {
     '#title' => t('Show components on create/edit feature form.'),
     '#description' => t('Components with no options will not be shown no matter the setting below. Disabled components cannot be used with admin form.')
   );
-  foreach ($components as $compontent => $info) {
+
+  $form['lock_components'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Lock components'),
+    '#description' => t('Locked components will be prevented from ever being reverted. For example, if site builder updates a feature with new settings for a field instance, but field instance is locked, it will not update that field. If the item is purely in code, like a view, the view changed when the code is updated no matter these settings.')
+  );
+  $form['features_lock_mode'] = array(
+    '#type' => 'radios',
+    '#title' => t('Features lock mode'),
+    '#options' => array(
+      'rebuild' => t('Allow rebuild (prevent revert)'),
+      'all' => t('Prevent rebuild and revert'),
+    ),
+    '#description' => t('Rebuild will allow the feature to be updated till the point features has detected that the item has changed deliberately on the site, e.g. is overriden.'),
+    '#default_value' => variable_get('features_lock_mode', 'all'),
+  );
+  foreach ($components as $component => $info) {
     if (empty($info['feature_source']) && empty($info['features_source'])) {
       continue;
     }
-    $form['show_components']['features_admin_show_component_' . $compontent] = array(
-      '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $compontent)),
+    $form['show_components']['features_admin_show_component_' . $component] = array(
+      '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $component)),
       '#type' => 'checkbox',
-      '#default_value' => variable_get('features_admin_show_component_' . $compontent, TRUE),
+      '#default_value' => variable_get('features_admin_show_component_' . $component, TRUE),
     );
-    if ($compontent == 'menu_links' && ($menus = menu_get_menus())) {
+    if (features_hook($component, 'features_revert') || features_hook($component, 'features_rebuild')) {
+      $form['lock_components']['features_component_locked_' . $component] = array(
+        '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $component)),
+        '#type' => 'checkbox',
+        '#default_value' => variable_get('features_component_locked_' . $component, FALSE),
+      );
+    }
+    if ($component == 'menu_links' && ($menus = menu_get_menus())) {
       $form['show_components']['features_admin_menu_links'] = array(
         '#title' => t('Advanced Menu Link Settings'),
         '#type' => 'fieldset',
@@ -104,23 +127,24 @@ function features_export_form($form, $form_state, $feature = NULL) {
     '#description' => t('Example: Image gallery') . ' (' . t('Do not begin name with numbers.') . ')',
     '#type' => 'textfield',
     '#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '',
-    '#attributes' => array('class' => array('feature-name')),
   );
   $form['info']['module_name'] = array(
-    '#type' => 'textfield',
+    '#type' => 'machine_name',
     '#title' => t('Machine-readable name'),
     '#description' => t('Example: image_gallery') . '<br/>' . t('May only contain lowercase letters, numbers and underscores. <strong>Try to avoid conflicts with the names of existing Drupal projects.</strong>'),
     '#required' => TRUE,
     '#default_value' => $feature_name,
-    '#attributes' => array('class' => array('feature-module-name')),
-    '#element_validate' => array('features_export_form_validate_field'),
+    '#machine_name' => array(
+      'exists' => 'features_export_form_module_name_exists',
+      'source' => array('info', 'name'),
+    ),
   );
-  // If recreating this feature, disable machine name field and blank out
-  // js-attachment classes to ensure the machine name cannot be changed.
-  if (isset($feature)) {
+  // If recreating this feature, disable machine name field to ensure the
+  // machine name cannot be changed, unless user role has granted permission to
+  // edit machine name of disabled features.
+  if (isset($feature) && ($feature->status || !user_access('rename features'))) {
     $form['info']['module_name']['#value'] = $feature_name;
     $form['info']['module_name']['#disabled'] = TRUE;
-    $form['info']['name']['#attributes'] = array();
   }
   $form['info']['description'] = array(
     '#title' => t('Description'),
@@ -223,6 +247,13 @@ function features_export_form($form, $form_state, $feature = NULL) {
   return $form;
 }
 
+/**
+ * Machine name existence callback for the module name.
+ */
+function features_export_form_module_name_exists($value) {
+  return (bool) features_get_info('module', $value);
+}
+
 /**
  * Return the render array elements for the Components selection on the Export form
  * @param  array $feature    - feature associative array
@@ -779,16 +810,6 @@ function features_info_file_preview($form, &$form_state){
  */
 function features_export_form_validate_field($element, &$form_state) {
   switch ($element['#name']) {
-    case 'module_name':
-      if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
-        form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
-      }
-      // If user is filling out the feature name for the first time and uses
-      // the name of an existing module throw an error.
-      else if (empty($element['#default_value']) && features_get_info('module', $element['#value'])) {
-        form_error($element, t('A module by the name @name already exists on your site. Please choose a different name.', array('@name' => $element['#value'])));
-      }
-      break;
     case 'project_status_url':
       if (!empty($element['#value']) && !valid_url($element['#value'])) {
         form_error($element, t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $element['#value'])));
@@ -988,7 +1009,7 @@ function features_admin_form($form, $form_state) {
   ksort($features);
   foreach ($features as $name => $module) {
     $package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other');
-    $package = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title));
+    $package = 'package_' . strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title));
 
     // Set up package elements
     if (!isset($form[$package])) {
@@ -1052,6 +1073,7 @@ function features_admin_form($form, $form_state) {
     }
 
     $href = "admin/structure/features/{$name}";
+    $href_overridden = module_exists('diff') ? $href . '/diff' : $href;
     $module_name = (user_access('administer features')) ? l($module->info['name'], $href) : $module->info['name'];
     $form[$package]['status'][$name] = array(
       '#type' => 'checkbox',
@@ -1081,7 +1103,7 @@ function features_admin_form($form, $form_state) {
         $state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check'))));
         $state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href));
         $state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' =>  $href));
-        $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' =>  $href));
+        $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' =>  $href_overridden));
         $state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' =>  $href));
       }
       elseif (!empty($conflicts[$name])) {
@@ -1131,7 +1153,7 @@ function features_admin_components($form, $form_state, $feature) {
   drupal_set_breadcrumb($breadcrumb);
 
   module_load_include('inc', 'features', 'features.export');
-  $form = array();
+  $form['#feature'] = $feature;
 
   // Store feature info for theme layer.
   $form['module'] = array('#type' => 'value', '#value' => $feature->name);
@@ -1195,8 +1217,14 @@ function features_admin_components($form, $form_state, $feature) {
     else if (array_key_exists($component, $conflicts)) {
       $storage = FEATURES_CONFLICT;
     }
+    // This can be removed if the css is fixed so link doesn't move when
+    // ajaxing and linke moved.
+    $lock_link = '<span class="features-lock-empty"></span>';
+    if (user_access('administer features') && (features_hook($component, 'features_revert') || features_hook($component, 'features_rebuild'))) {
+      $lock_link = ' ' . theme('features_lock_link', array('feature' => $feature->name, 'component' => $component));
+    }
     $form['components'][$component] = array(
-      '#markup' => theme('features_storage_link', array('storage' => $storage, 'path' =>  $path)),
+      '#markup' => $lock_link . theme('features_storage_link', array('storage' => $storage, 'path' =>  $path)),
     );
   }
 
@@ -1367,6 +1395,71 @@ function features_feature_diff($feature, $component = NULL) {
   return $output;
 }
 
+
+
+/**
+ * Page callback to lock a component.
+ *
+ * @param $feature
+ *  Loaded feature object to be processed for component locking.
+ * @param $component
+ *   (optional) A specific component to lock.
+ *
+ * @return
+ *   Themed display of what is different.
+ */
+function features_admin_lock($feature, $type = 'ajax', $component = NULL) {
+  if ($type == 'ajax' && !empty($_GET['token']) && drupal_valid_token($_GET['token'], 'features/' . $feature->name . '/' . ($component ? $component : '')) == $_GET['token']) {
+    if (features_feature_is_locked($feature->name, $component, FALSE)) {
+      features_feature_unlock($feature->name, $component);
+    }
+    else {
+      features_feature_lock($feature->name, $component);
+    }
+    $commands = array();
+    $new_link = theme('features_lock_link', array('feature' => $feature->name, 'component' => $component));
+    $commands[] = ajax_command_replace('#features-lock-link-' . $feature->name . ($component ? '-' . $component : ''),  $new_link);
+    $page = array('#type' => 'ajax', '#commands' => $commands);
+    ajax_deliver($page);
+  }
+  else {
+    return drupal_get_form('features_feature_lock_confirm_form', $feature, $component);
+  }
+}
+
+/**
+ * Confirm form for locking a feature.
+ */
+function features_feature_lock_confirm_form($form, $form_state, $feature, $component) {
+  $form['#feature'] = $feature;
+  $form['#component'] = $component;
+  $is_locked = features_feature_is_locked($feature->name, $component, FALSE);
+  $args = array(
+    '@name' => $feature->name,
+    '@component' => $component ? $component : t('all'),
+    '!action' => $is_locked ? t('unlock') : t('lock'),
+  );
+  $question = t('Are you sure you want to !action this Feature @name (component @component)?', $args);
+  return confirm_form($form, $question, 'admin/structure/features/' . $feature->name);
+}
+
+/**
+ * Submit callback to lock components of a feature.
+ */
+function features_feature_lock_confirm_form_submit($form, &$form_state) {
+  $feature = $form['#feature']->name;
+  $component = $form['#component'];
+  if (features_feature_is_locked($feature, $component, FALSE)) {
+    features_feature_unlock($feature, $component);
+    drupal_set_message(t('Feature @name (component @component) has been unlocked.', array('@name' => $feature, '@component' => $component ? $component : t('all'))));
+  }
+  else {
+    features_feature_lock($feature, $component);
+    drupal_set_message(t('Feature @name (component @component) has been locked.', array('@name' => $feature, '@component' => $component ? $component : t('all'))));
+  }
+  $form_state['redirect'] = 'admin/structure/features/' . $feature;
+}
+
 /**
  * Compare the component names. Used to sort alphabetically.
  */

+ 56 - 0
sites/all/modules/contrib/admin/features/features.api.php

@@ -219,6 +219,42 @@ function hook_features_rebuild($module_name) {
   }
 }
 
+/**
+ * Invoked before a restore operation is run.
+ *
+ * This hook is called before any of the restore operations on the components is
+ * run.
+ *
+ * @param string $op
+ *   The operation that is triggered: revert, rebuild, disable, enable
+ * @param array $items
+ *   The items handled by the operation.
+ */
+function hook_features_pre_restore($op, $items) {
+  if ($op == 'rebuild') {
+    // Use features rebuild to rebuild the features independent exports too.
+    entity_defaults_rebuild();
+  }
+}
+
+/**
+ * Invoked after a restore operation is run.
+ *
+ * This hook is called after any of the restore operations on the components is
+ * run.
+ *
+ * @param string $op
+ *   The operation that is triggered: revert, rebuild, disable, enable
+ * @param array $items
+ *   The items handled by the operation.
+ */
+function hook_features_post_restore($op, $items) {
+  if ($op == 'rebuild') {
+    // Use features rebuild to rebuild the features independent exports too.
+    entity_defaults_rebuild();
+  }
+}
+
 /**
  * Alter the final array of Component names to be exported, just prior to
  * the rendering of defaults. Allows modules a final say in whether or not
@@ -293,6 +329,8 @@ function hook_features_pipe_alter(&$pipe, $data, $export) {
  */
 
 /**
+ * Deprecated as of 7.x-2.0.
+ *
  * Alter the default fields right before they are cached into the database.
  *
  * @param &$fields
@@ -301,6 +339,24 @@ function hook_features_pipe_alter(&$pipe, $data, $export) {
 function hook_field_default_fields_alter(&$fields) {
 }
 
+/**
+ * Alter the base fields right before they are cached into the database.
+ *
+ * @param &$fields
+ *   By reference. The fields that have been declared by another feature.
+ */
+function hook_field_default_field_bases_alter(&$fields) {
+}
+
+/**
+ * Alter the field instances right before they are cached into the database.
+ *
+ * @param &$fields
+ *   By reference. The fields that have been declared by another feature.
+ */
+function hook_field_default_field_instances_alter(&$fields) {
+}
+
 /**
  * Alter the default fieldgroup groups right before they are cached into the
  * database.

+ 13 - 0
sites/all/modules/contrib/admin/features/features.css

@@ -563,4 +563,17 @@ input.form-submit.features-refresh-button {
 fieldset.features-export-component .fieldset-title .component-count {
 font-size: 12px;
 font-weight: bold;
+}
+
+.features-lock-icon {
+  display: inline-block;
+}
+
+.features-components h3 {
+  display: inline-block;
+}
+
+.features-lock-empty {
+  display: inline-block;
+  width: 16px;
 }

+ 104 - 16
sites/all/modules/contrib/admin/features/features.drush.inc

@@ -43,6 +43,7 @@ function features_drush_command() {
       'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to '" . $path . "'.",
       'version-set' => "Specify a version number for the feature.",
       'version-increment' => "Increment the feature's version number.",
+      'ignore-conflicts' => "Ignore conflicts and export all components.",
     ),
     'drupal dependencies' => array('features'),
     'aliases' => array('fe'),
@@ -72,6 +73,9 @@ function features_drush_command() {
       'not-exported' => array(
         'description' => 'Show only components that have not been exported.',
       ),
+      'info-style' => array(
+        'description' => 'Export components in format suitable for using in an info file.',
+      ),
     ),
     'aliases' => array('fc'),
   );
@@ -134,6 +138,18 @@ function features_drush_command() {
     'aliases' => array('fd'),
   );
 
+  $items['features-diff-all'] = array(
+    'description' => "Show the code difference for all enabled features not in their default state.",
+    'arguments' => array(
+      'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.',
+    ),
+    'options' => array(
+      'force' => "Bypass the confirmations. This is useful if you want to output all of the diffs to a log file.",
+    ),
+    'drupal dependencies' => array('features', 'diff'),
+    'aliases' => array('fda'),
+  );
+
   return $items;
 }
 
@@ -232,6 +248,7 @@ function drush_features_list() {
 function drush_features_components() {
   $args = func_get_args();
   $components = _drush_features_component_list();
+  ksort($components);
   // If no args supplied, prompt with a list.
   if (empty($args)) {
     $types = array_keys($components);
@@ -252,10 +269,13 @@ function drush_features_components() {
   elseif (drush_get_option(array('not-exported', 'o'), NULL)) {
     $options['exported'] = FALSE;
   }
+  if (drush_get_option(array('info-style', 'is'), NULL)) {
+    $options['info style'] = TRUE;
+  }
 
   $filtered_components = _drush_features_component_filter($components, $args, $options);
   if ($filtered_components){
-    _drush_features_component_print($filtered_components);
+    _drush_features_component_print($filtered_components, $options);
   }
 }
 
@@ -289,7 +309,7 @@ function _drush_features_component_filter($all_components, $patterns = array(),
   // First filter on exported state.
   foreach ($all_components as $source => $components) {
     foreach ($components as $name => $title) {
-      $exported = sizeof($components_map[$source][$name]) > 0;
+      $exported = !empty($components_map[$source][$name]);
       if ($exported) {
         if ($options['exported']) {
           $pool[$source][$name] = $title;
@@ -317,7 +337,8 @@ function _drush_features_component_filter($all_components, $patterns = array(),
     // Rewrite * to %. Let users use both as wildcard.
     $pattern = strtr($pattern, array('*' => '%'));
     $sources = array();
-    list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
+    $source_pattern = strtok($pattern, ':');
+    $component_pattern = strtok(':');
     // If source is empty, use a pattern.
     if ($source_pattern == '') {
       $source_pattern = '%';
@@ -382,7 +403,7 @@ function _drush_features_component_filter($all_components, $patterns = array(),
             return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches))));
           }
         }
-        if (!is_array($selected[$source])) {
+        if (empty($selected[$source])) {
           $selected[$source] = array();
         }
         $selected[$source] += array_intersect_key($pool[$source], array_flip($matches));
@@ -403,7 +424,7 @@ function _drush_features_component_filter($all_components, $patterns = array(),
   if ($options['provided by'] && $options['exported'] ) {
     foreach ($selected as $source => $components) {
       foreach ($components as $name => $title) {
-        $exported = sizeof($components_map[$source][$name]) > 0;
+        $exported = !empty($components_map[$source][$name]);
         if ($exported) {
           $provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]);
         }
@@ -420,11 +441,17 @@ function _drush_features_component_filter($all_components, $patterns = array(),
 /**
  * Prints a list of filtered components.
  */
-function _drush_features_component_print($filtered_components) {
+function _drush_features_component_print($filtered_components, $options = array()) {
   $rows = array(array(dt('Available sources')));
   foreach ($filtered_components['components'] as $source => $components) {
     foreach ($components as $name => $value) {
-      $row = array($source .':'. $name);
+      if (!empty($options['info style'])) {
+        // Output as .info file style.
+        $row = array('features[' . $source . '][] = "' . $name . '"');
+      }
+      else {
+        $row = array($source .':'. $name);
+      }
       if (isset($filtered_components['sources'][$source .':'. $name])) {
         $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name];
       }
@@ -446,9 +473,11 @@ function drush_features_export() {
       return drush_set_error('', 'No components supplied.');
     }
     $components = _drush_features_component_list();
-    $options = array(
-      'exported' => FALSE,
-    );
+    $options = array();
+
+    if (!drush_get_option('ignore-conflicts', FALSE)) {
+      $options['exported'] = FALSE;
+    }
 
     $filtered_components = _drush_features_component_filter($components, $args, $options);
     $items = $filtered_components['components'];
@@ -568,11 +597,9 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
     $destination = drush_get_option(array('destination'), variable_get('features_default_export_path', FEATURES_DEFAULT_EXPORT_PATH));
     $directory = isset($directory) ? $directory : $destination . '/' . $module_name;
     if (is_dir($directory)) {
-      $warning = dt('Module appears to already exist in !dir', array('!dir' => $directory));
-      drush_log($warning, 'warning');
       // If we aren't skipping confirmation and the directory already exists,
-      // prompt the user.
-      if (!$skip_confirmation && !drush_confirm(dt('Do you really want to continue?'))) {
+      // prompt the user. This message most make sense for but fe and fu.
+      if (!$skip_confirmation && !drush_confirm(dt('Module located at !dir will be updated. Do you want to continue?', array('!dir' => $directory)))) {
         drush_die('Aborting.');
       }
     }
@@ -725,8 +752,13 @@ function drush_features_revert() {
             $dt_args['@component'] = $component;
             $confirmation_message = 'Do you really want to revert @module.@component?';
             if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
-              features_revert(array($module => array($component)));
-              drush_log(dt('Reverted @module.@component.', $dt_args), 'ok');
+              if (features_feature_is_locked($module, $component)) {
+                drush_log(dt('Skipping locked @module.@component.', $dt_args), 'ok');
+              }
+              else {
+                features_revert(array($module => array($component)));
+                drush_log(dt('Reverted @module.@component.', $dt_args), 'ok');
+              }
             }
             else {
               drush_log(dt('Skipping @module.@component.', $dt_args), 'ok');
@@ -882,6 +914,62 @@ function drush_features_diff() {
   }
 }
 
+/**
+ * Diff all enabled features that are not in their default state.
+ *
+ * @param ...
+ *   (Optional) A list of features to exclude from being reverted.
+ */
+function drush_features_diff_all() {
+  module_load_include('inc', 'features', 'features.export');
+  $features_to_exclude = func_get_args();
+
+  $features_to_revert = array();
+  foreach (features_get_features(NULL, TRUE) as $module) {
+    if ($module->status && !in_array($module->name, $features_to_exclude)) {
+      switch (features_get_storage($module->name)) {
+        case FEATURES_OVERRIDDEN:
+        case FEATURES_NEEDS_REVIEW:
+        case FEATURES_REBUILDABLE:
+          $features_to_diff[] = $module->name;
+          break;
+      }
+    }
+  }
+
+  if ($features_to_diff) {
+
+    // Check if the user wants to apply the force option.
+    $force = drush_get_option('force');
+
+    if($force) {
+      foreach ($features_to_diff as $module) {
+
+        drush_print(dt('Diff for !module:', array('!module' => $module)));
+
+        drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
+      }
+    }
+    else {
+
+      drush_print(dt('A diff will be performed for the following modules: !modules',
+        array('!modules' => implode(', ', $features_to_diff))
+      ));
+
+      if (drush_confirm(dt('Do you want to continue?'))) {
+        foreach ($features_to_diff as $module) {
+          if (drush_confirm(dt('Diff !module?', array('!module' => $module)))) {
+            drush_invoke_process(drush_sitealias_get_record('@self'), 'features-diff', array($module));
+          }
+        }
+      }
+      else {
+        return drush_user_abort('Aborting.');
+      }
+    }
+  }
+}
+
 /**
  * Helper function to call drush_set_error().
  *

+ 12 - 15
sites/all/modules/contrib/admin/features/features.export.inc

@@ -45,11 +45,11 @@ function features_populate($info, $module_name) {
  * @return fully populated $export array.
  */
 function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
-  static $processed = array();
-  features_include();
   if ($reset) {
-    $processed = array();
+    drupal_static_reset(__FUNCTION__);
   }
+  $processed = &drupal_static(__FUNCTION__, array());
+  features_include();
   foreach ($pipe as $component => $data) {
     // Convert already defined items to dependencies.
 //    _features_resolve_dependencies($data, $export, $module_name, $component);
@@ -385,10 +385,7 @@ function features_export_render($export, $module_name, $reset = FALSE) {
  * Detect differences between DB and code components of a feature.
  */
 function features_detect_overrides($module) {
-  static $cache;
-  if (!isset($cache)) {
-    $cache = array();
-  }
+  $cache = &drupal_static(__FUNCTION__, array());
   if (!isset($cache[$module->name])) {
     // Rebuild feature from .info file description and prepare an export from current DB state.
     $export = features_populate($module->info, $module->name);
@@ -713,10 +710,10 @@ function features_semaphore($op, $component) {
  * Get normal objects for a given module/component pair.
  */
 function features_get_normal($component, $module_name, $reset = FALSE) {
-  static $cache;
-  if (!isset($cache) || $reset) {
-    $cache = array();
+  if ($reset) {
+    drupal_static_reset(__FUNCTION__);
   }
+  $cache = &drupal_static(__FUNCTION__, array());
   if (!isset($cache[$module_name][$component])) {
     features_include();
     $code = NULL;
@@ -746,7 +743,7 @@ function features_get_normal($component, $module_name, $reset = FALSE) {
  * Get defaults for a given module/component pair.
  */
 function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) {
-  static $cache = array();
+  $cache = &drupal_static(__FUNCTION__, array());
   $alter = !empty($alter); // ensure $alter is a true/false boolean
   features_include();
   features_include_defaults($component);
@@ -820,7 +817,7 @@ function features_get_default($component, $module_name = NULL, $alter = TRUE, $r
  * Get a map of components to their providing modules.
  */
 function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) {
-  static $map = array();
+  $map = &drupal_static(__FUNCTION__, array());
 
   global $features_ignore_conflicts;
   if ($features_ignore_conflicts) {
@@ -866,10 +863,10 @@ function features_get_default_map($component, $attribute = NULL, $callback = NUL
  * Retrieve an array of features/components and their current states.
  */
 function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) {
-  static $cache;
-  if (!isset($cache) || $reset) {
-    $cache = array();
+  if ($reset) {
+    drupal_static_reset(__FUNCTION__);
   }
+  $cache = &drupal_static(__FUNCTION__, array());
 
   $all_features = features_get_features();
   $features = !empty($features) ? $features : array_keys($all_features);

+ 3 - 3
sites/all/modules/contrib/admin/features/features.info

@@ -6,9 +6,9 @@ files[] = tests/features.test
 
 configure = admin/structure/features/settings
 
-; Information added by drupal.org packaging script on 2013-10-17
-version = "7.x-2.0+0-dev"
+; Information added by Drupal.org packaging script on 2015-04-13
+version = "7.x-2.5"
 core = "7.x"
 project = "features"
-datestamp = "1382036080"
+datestamp = "1428944073"
 

+ 11 - 1
sites/all/modules/contrib/admin/features/features.install

@@ -25,6 +25,16 @@ function features_uninstall() {
   variable_del('features_default_export_path');
   variable_del('features_semaphore');
   variable_del('features_ignored_orphans');
+  variable_del('features_feature_locked');
+  variable_del('features_lock_mode');
+
+  db_delete('variable')
+    ->condition('name', 'features_admin_show_component_%', 'LIKE')
+    ->execute();
+  db_delete('variable')
+    ->condition('name', 'features_component_locked_%', 'LIKE')
+    ->execute();variable_del('features_component_locked_' . $component);
+
   if (db_table_exists('menu_custom')) {
     db_delete('menu_custom')
       ->condition('menu_name', 'features')
@@ -53,7 +63,7 @@ function _features_install_menu() {
 
 /**
  * Update 6100: Set module on all feature node types to 'features'.
-
+ *
  * This update can be re-run as needed to repair any node types that are not
  * removed after disabling the associated feature.
  *

+ 0 - 28
sites/all/modules/contrib/admin/features/features.js

@@ -100,33 +100,6 @@ jQuery.fn.sortElements = (function(){
         }).trigger('change');
       });
 
-      // Export form machine-readable JS
-      $('.feature-name:not(.processed)', context).each(function() {
-        $('.feature-name')
-          .addClass('processed')
-          .after(' <small class="feature-module-name-suffix">&nbsp;</small>');
-        if ($('.feature-module-name').val() === $('.feature-name').val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_') || $('.feature-module-name').val() === '') {
-          $('.feature-module-name').parents('.form-item').hide();
-          $('.feature-name').bind('keyup change', function() {
-            var machine = $(this).val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_');
-            if (machine !== '_' && machine !== '') {
-              $('.feature-module-name').val(machine);
-              $('.feature-module-name-suffix').empty().append(' Machine name: ' + machine + ' [').append($('<a href="#">'+ Drupal.t('Edit') +'</a>').click(function() {
-                $('.feature-module-name').parents('.form-item').show();
-                $('.feature-module-name-suffix').hide();
-                $('.feature-name').unbind('keyup');
-                return false;
-              })).append(']');
-            }
-            else {
-              $('.feature-module-name').val(machine);
-              $('.feature-module-name-suffix').text('');
-            }
-          });
-          $('.feature-name').keyup();
-        }
-      });
-
       //View info dialog
       var infoDialog = $('#features-info-file');
       if (infoDialog.length != 0) {
@@ -184,7 +157,6 @@ jQuery.fn.sortElements = (function(){
       }
 
       function updateComponentCountInfo(item, section) {
-        console.log(section);
         switch (section) {
           case 'select':
             var parent = $(item).closest('.features-export-list').siblings('.features-export-component');

File diff suppressed because it is too large
+ 25 - 5
sites/all/modules/contrib/admin/features/features.module


+ 37 - 3
sites/all/modules/contrib/admin/features/includes/features.field.inc

@@ -151,7 +151,7 @@ function field_base_features_export_render($module, $data, $export = NULL) {
   foreach ($data as $identifier) {
     if ($field = features_field_base_load($identifier)) {
       unset($field['columns']);
-      // unset($field['locked']);
+      unset($field['foreign keys']);
       // Only remove the 'storage' declaration if the field is using the default
       // storage type.
       if ($field['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) {
@@ -166,8 +166,15 @@ function field_base_features_export_render($module, $data, $export = NULL) {
 
       _field_instance_features_export_sort($field);
       $field_export = features_var_export($field, '  ');
+      $field_prefix = '  // Exported field_base: ';
       $field_identifier = features_var_export($identifier);
-      $code[] = "  // Exported field_base: {$field_identifier}";
+      if (features_field_export_needs_wrap($field_prefix, $field_identifier)) {
+        $code[] = rtrim($field_prefix);
+        $code[] = "  // {$field_identifier}";
+      }
+      else {
+        $code[] = $field_prefix . $field_identifier;
+      }
       $code[] = "  \$field_bases[{$field_identifier}] = {$field_export};";
       $code[] = "";
     }
@@ -190,8 +197,15 @@ function field_instance_features_export_render($module, $data, $export = NULL) {
     if ($instance = features_field_instance_load($identifier)) {
       _field_instance_features_export_sort($instance);
       $field_export = features_var_export($instance, '  ');
+      $instance_prefix = '  // Exported field_instance: ';
       $instance_identifier = features_var_export($identifier);
-      $code[] = "  // Exported field_instance: {$instance_identifier}";
+      if (features_field_export_needs_wrap($instance_prefix, $instance_identifier)) {
+        $code[] = rtrim($instance_prefix);
+        $code[] = "  // {$instance_identifier}";
+      }
+      else {
+        $code[] = $instance_prefix . $instance_identifier;
+      }
       $code[] = "  \$field_instances[{$instance_identifier}] = {$field_export};";
       $code[] = "";
 
@@ -528,3 +542,23 @@ function features_field_load($identifier) {
   }
   return FALSE;
 }
+
+/**
+ * Determine if a field export line needs to be wrapped.
+ *
+ * Drupal code standards specify that comments should wrap at 80 characters or
+ * less.
+ *
+ * @param string $prefix
+ *   The prefix to be exported before the field identifier.
+ * @param string $identifier
+ *   The field identifier.
+ *
+ * @return BOOL
+ *   TRUE if the line should be wrapped after the prefix, else FALSE.
+ *
+ * @see https://www.drupal.org/node/1354
+ */
+function features_field_export_needs_wrap($prefix, $identifier) {
+  return (strlen($prefix) + strlen($identifier) > 80);
+}

+ 20 - 11
sites/all/modules/contrib/admin/features/includes/features.image.inc

@@ -86,16 +86,25 @@ function image_features_revert($module) {
 /**
  * Remove unnecessary keys for export.
  */
-function _image_features_style_sanitize(&$style, $child = FALSE) {
-  $omit = $child ? array('isid', 'ieid', 'storage') : array('isid', 'ieid', 'storage', 'module');
-  if (is_array($style)) {
-    foreach ($style as $k => $v) {
-      if (in_array($k, $omit, TRUE)) {
-        unset($style[$k]);
-      }
-      else if (is_array($v)) {
-        _image_features_style_sanitize($style[$k], TRUE);
-      }
-    }
+function _image_features_style_sanitize(array &$style) {
+  // Sanitize style: Don't export numeric IDs and things which get overwritten
+  // in image_styles() or are code/storage specific. The name property will be
+  // the key of the exported $style array.
+  $style = array_diff_key($style, array_flip(array(
+    'isid',
+    'name',
+    'module',
+    'storage',
+  )));
+
+  // Sanitize effects: all that needs to be kept is name, weight and data,
+  // which holds all the style-specific configuration. Other keys are assumed
+  // to belong to the definition of the effect itself, so not configuration.
+  foreach ($style['effects'] as $id => $effect) {
+    $style['effects'][$id] = array_intersect_key($effect, array_flip(array(
+      'name',
+      'data',
+      'weight',
+    )));
   }
 }

+ 1 - 0
sites/all/modules/contrib/admin/features/includes/features.locale.inc

@@ -134,6 +134,7 @@ function _features_language_save($language) {
       ->fields(array(
         'plurals' => empty($language->plurals) ? 0 : $language->plurals,
         'formula' => empty($language->formula) ? '' : $language->formula,
+        'weight' => empty($language->weight) ? 0 : $language->weight,
       ))
       ->condition('language', $language->language)
       ->execute();

+ 31 - 18
sites/all/modules/contrib/admin/features/includes/features.menu.inc

@@ -241,7 +241,8 @@ function menu_links_features_export_render($module, $data, $export = NULL) {
         // Don't show new identifier unless we are actually exporting.
         $link['options']['identifier'] = $new_identifier;
         // identifiers are renewed, => that means we need to update them in the db
-        menu_link_save($temp = $link);
+        $temp = $link;
+        menu_link_save($temp);
       }
 
        unset($link['plid']);
@@ -295,19 +296,27 @@ function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) {
         foreach ($unordered as $key => $link) {
           $identifier = menu_links_features_identifier($link);
           $parent = isset($link['parent_identifier']) ? $link['parent_identifier'] : '';
-          if (empty($parent)) {
-            $ordered[$identifier] = 0;
-            $all_links[$identifier] = $link;
-            unset($unordered[$key]);
+          $weight = 0;
+          // Parent has been seen, so weigh this above parent.
+          if (isset($ordered[$parent])) {
+            $weight = $ordered[$parent] + 1;
           }
-          elseif (isset($ordered[$parent])) {
-            $ordered[$identifier] = $ordered[$parent] + 1;
-            $all_links[$identifier] = $link;
-            unset($unordered[$key]);
+          // Next loop will try to find parent weight instead.
+          elseif ($parent) {
+            continue;
           }
+          $ordered[$identifier] = $weight;
+          $all_links[$identifier] = $link;
+          unset($unordered[$key]);
         }
+      // Exit out when the above does no changes this loop.
       } while (count($unordered) < $current);
     }
+    // Add all remaining unordered items to the ordered list.
+    foreach ($unordered as $link) {
+      $identifier = menu_links_features_identifier($link);
+      $ordered[$identifier] = 0;
+    }
     asort($ordered);
   }
 
@@ -356,10 +365,11 @@ function features_menu_link_load($identifier) {
     list($menu_name, $link_path) = explode(':', $identifier, 2);
   }
   $links = db_select('menu_links')
-     ->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight', 'customized'))
-     ->condition('menu_name', $menu_name)
-     ->condition('link_path', $link_path)
-     ->execute()
+    ->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight', 'customized'))
+    ->condition('menu_name', $menu_name)
+    ->condition('link_path', $link_path)
+    ->addTag('features_menu_link')
+    ->execute()
     ->fetchAllAssoc('mlid');
 
   foreach($links as $link) {
@@ -391,11 +401,14 @@ function features_menu_link_load($identifier) {
 
     foreach($links as $link) {
       $link->options = unserialize($link->options);
-
-      // title or previous identifier matches
-      if ((isset($link->options['identifier']) && strcmp($link->options['identifier'], $identifier) == 0)
-          || (isset($clean_title) && strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
-
+      // Links with a stored identifier must only be matched on that identifier,
+      // to prevent cross over assumptions.
+      if (isset($link->options['identifier'])) {
+        if (strcmp($link->options['identifier'], $identifier) == 0) {
+          return (array)$link;
+        }
+      }
+      elseif ((strcmp(features_clean_title($link->link_title), $clean_title) == 0)) {
         return (array)$link;
       }
     }

+ 8 - 4
sites/all/modules/contrib/admin/features/includes/features.node.inc

@@ -9,6 +9,7 @@ function node_features_api() {
       'name' => t('Content types'),
       'feature_source' => TRUE,
       'default_hook' => 'node_info',
+      'alter_type' => FEATURES_ALTER_TYPE_INLINE,
     ),
   );
 }
@@ -86,6 +87,7 @@ function node_features_export_render($module, $data, $export = NULL) {
     }
   }
   $output[] = '  );';
+  $output[] = '  drupal_alter(\'node_info\', $items);';
   $output[] = '  return $items;';
   $output = implode("\n", $output);
   return array('node_info' => $output);
@@ -115,7 +117,7 @@ function node_features_revert($module = NULL) {
 }
 
 /**
- * Implements hook_features_disable().
+ * Implements hook_features_disable_feature().
  *
  * When a features module is disabled, modify any node types it provides so
  * they can be deleted manually through the content types UI.
@@ -123,7 +125,7 @@ function node_features_revert($module = NULL) {
  * @param $module
  *   Name of module that has been disabled.
  */
-function node_features_disable($module) {
+function node_features_disable_feature($module) {
   if ($default_types = features_get_default('node', $module)) {
     foreach ($default_types as $type_name => $type_info) {
       $type_info = node_type_load($type_name);
@@ -131,13 +133,14 @@ function node_features_disable($module) {
       $type_info->custom = 1;
       $type_info->modified = 1;
       $type_info->locked = 0;
+      $type_info->disabled = 0;
       node_type_save($type_info);
     }
   }
 }
 
 /**
- * Implements hook_features_enable().
+ * Implements hook_features_enable_feature().
  *
  * When a features module is enabled, modify any node types it provides so
  * they can no longer be deleted manually through the content types UI.
@@ -145,7 +148,7 @@ function node_features_disable($module) {
  * @param $module
  *   Name of module that has been enabled.
  */
-function node_features_enable($module) {
+function node_features_enable_feature($module) {
   if ($default_types = features_get_default('node', $module)) {
     foreach ($default_types as $type_name => $type_info) {
       // Ensure the type exists.
@@ -154,6 +157,7 @@ function node_features_enable($module) {
         $type_info->custom = 0;
         $type_info->modified = 0;
         $type_info->locked = 1;
+        $type_info->disabled = 0;
         node_type_save($type_info);
       }
     }

+ 2 - 2
sites/all/modules/contrib/admin/features/includes/features.user.inc

@@ -50,12 +50,12 @@ function user_permission_features_export_options() {
   $modules = array();
   $module_info = system_get_info('module');
   foreach (module_implements('permission') as $module) {
-    $modules[$module_info[$module]['name']] = $module;
+    $modules[$module] = $module_info[$module]['name'];
   }
   ksort($modules);
 
   $options = array();
-  foreach ($modules as $display_name => $module) {
+  foreach ($modules as $module => $display_name) {
     if ($permissions = module_invoke($module, 'permission')) {
       foreach ($permissions as $perm => $perm_item) {
         // Export vocabulary permissions using the machine name, instead of

+ 0 - 8
sites/all/modules/contrib/admin/features/tests/features_test/features_test.features.inc

@@ -29,16 +29,8 @@ function features_test_image_default_styles() {
 
   // Exported image style: features_test.
   $styles['features_test'] = array(
-    'name' => 'features_test',
     'effects' => array(
       2 => array(
-        'label' => 'Scale',
-        'help' => 'Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.',
-        'effect callback' => 'image_scale_effect',
-        'dimensions callback' => 'image_scale_dimensions',
-        'form callback' => 'image_scale_form',
-        'summary theme' => 'image_scale_summary',
-        'module' => 'image',
         'name' => 'image_scale',
         'data' => array(
           'width' => 100,

+ 3 - 3
sites/all/modules/contrib/admin/features/tests/features_test/features_test.info

@@ -21,9 +21,9 @@ features[user_permission][] = create features_test content
 features[views_view][] = features_test
 hidden = 1
 
-; Information added by drupal.org packaging script on 2013-10-17
-version = "7.x-2.0+0-dev"
+; Information added by Drupal.org packaging script on 2015-04-13
+version = "7.x-2.5"
 core = "7.x"
 project = "features"
-datestamp = "1382036080"
+datestamp = "1428944073"
 

+ 1 - 1
sites/all/modules/contrib/admin/features/theme/features-admin-components.tpl.php

@@ -3,7 +3,7 @@
 <div class='clearfix features-components'>
   <div class='column'>
     <div class='info'>
-      <h3><?php print $name ?></h3>
+      <?php print $lock_feature ?><h3><?php print $name ?></h3>
       <div class='description'><?php print $description ?></div>
       <?php print $dependencies ?>
     </div>

+ 31 - 0
sites/all/modules/contrib/admin/features/theme/theme.inc

@@ -81,6 +81,7 @@ function template_preprocess_features_admin_components(&$vars) {
   // Other elements
   $vars['buttons'] = drupal_render($form['buttons']);
   $vars['form'] = $form;
+  $vars['lock_feature'] = theme('features_lock_link', array('feature' => $form['#feature']->name));
 }
 
 /**
@@ -109,6 +110,36 @@ function theme_features_module_status($vars) {
   return "<span class=\"$class\">$text</span>";
 }
 
+/**
+ * Themes a lock link
+ */
+function theme_features_lock_link($vars) {
+  drupal_add_library('system', 'ui');
+  drupal_add_library('system', 'drupal.ajax');
+  $component = $vars['component'] ? $vars['component'] : '';
+  if ($component && features_component_is_locked($component)) {
+    return l(t('Component locked'), 'admin/structure/features/settings', array(
+      'attributes' => array(
+        'class' => 'features-lock-icon ui-icon ui-icon-locked',
+        'title' => t('This component is locked on a global level.'),
+      ),
+      'fragment' => 'edit-lock-components',
+    ));
+  }
+  $feature = $vars['feature'];
+  $is_locked = features_feature_is_locked($feature, $component);
+  $options = array(
+    'attributes' => array(
+      'class' => array('use-ajax features-lock-icon ui-icon ' . ($is_locked ? ' ui-icon-locked' : ' ui-icon-unlocked')),
+      'id' => 'features-lock-link-' . $feature . ($component ? '-' . $component : ''),
+      'title' => $is_locked ? t('This item is locked and features will not be rebuilt or reverted.') : t('This item is unlocked and will be rebuilt/reverted as normal.'),
+    ),
+    'query' => array('token' => drupal_get_token('features/' . $feature . '/' . $component)),
+  );
+  $path =  "admin/structure/features/" . $feature . "/lock/nojs" . ($component ? '/' . $component: '');
+  return l($is_locked ? t('UnLock') : t('Lock'), $path, $options);
+}
+
 /**
  * Themes a module status display.
  */

+ 3 - 3
sites/all/modules/contrib/admin/filter_perms/filter_perms.info

@@ -3,9 +3,9 @@ description = Provides role and module filters to simplify the user permissions
 package = Administration
 core = 7.x
 
-; Information added by drupal.org packaging script on 2012-05-15
-version = "7.x-1.x-dev"
+; Information added by drupal.org packaging script on 2013-09-30
+version = "7.x-1.0+0-dev"
 core = "7.x"
 project = "filter_perms"
-datestamp = "1337040981"
+datestamp = "1380579343"
 

+ 16 - 13
sites/all/modules/contrib/admin/google_analytics/README.txt

@@ -15,7 +15,7 @@ Requirements
 
 Installation
 ============
-* Copy the 'googleanalytics' module directory in to your Drupal
+Copy the 'googleanalytics' module directory in to your Drupal
 sites/all/modules directory as usual.
 
 
@@ -51,19 +51,22 @@ choice for "Add if the following PHP code returns TRUE." Sample PHP snippets
 that can be used in this textarea can be found on the handbook page
 "Overview-approach to block visibility" at http://drupal.org/node/64135.
 
-Custom variables
-=================
-One example for custom variables tracking is the "User roles" tracking. Enter
-the below configuration data into the custom variables settings form under
-admin/config/system/googleanalytics.
+Custom dimensions and metrics
+=============================
+One example for custom dimensions tracking is the "User roles" tracking.
+
+1. In the Google Analytics Management Interface you need to setup Dimension #1
+   with name e.g. "User roles". This step is required. Do not miss it, please.
+
+2. Enter the below configuration data into the custom dimensions settings form
+   under admin/config/system/googleanalytics. You can also choose another index,
+   but keep it always in sync with the index used in step #1.
 
-Slot: 1
-Name: User roles
-Value: [current-user:role-names]
-Scope: Visitor
+   Index: 1
+   Value: [current-user:role-names]
 
-More details about Custom variables can be found in the Google API documentation at
-http://code.google.com/intl/en/apis/analytics/docs/tracking/gaTrackingCustomVariables.html
+More details about custom dimensions and metrics can be found in the Google API
+documentation at https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets
 
 Advanced Settings
 =================
@@ -72,5 +75,5 @@ code textarea. These can be found on the official Google Analytics pages
 and a few examples at http://drupal.org/node/248699. Support is not
 provided for any customisations you include.
 
-To speed up page loading you may also cache the Analytics ga.js
+To speed up page loading you may also cache the Google Analytics "analytics.js"
 file locally.

File diff suppressed because it is too large
+ 39 - 5
sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc


+ 1 - 1
sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js

@@ -91,7 +91,7 @@ Drupal.behaviors.trackingSettingsSummary = {
         vals.push(Drupal.t('AdSense ads'));
       }
       if ($('input#edit-googleanalytics-trackdoubleclick', context).is(':checked')) {
-        vals.push(Drupal.t('DoubleClick data'));
+        vals.push(Drupal.t('Display features'));
       }
       if (!vals.length) {
         return Drupal.t('Not tracked');

+ 183 - 0
sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js

@@ -0,0 +1,183 @@
+(function ($) {
+
+Drupal.googleanalytics = {};
+
+$(document).ready(function() {
+
+  // Attach mousedown, keyup, touchstart events to document only and catch
+  // clicks on all elements.
+  $(document.body).bind("mousedown keyup touchstart", function(event) {
+    console.group("Running Google Analytics for Drupal.");
+    console.info(event);
+
+    // Catch the closest surrounding link of a clicked element.
+    $(event.target).closest("a,area").each(function() {
+      console.info("Element '%o' has been detected. Link '%s' found.", this, this.href);
+
+      // Is the clicked URL internal?
+      if (Drupal.googleanalytics.isInternal(this.href)) {
+        // Skip 'click' tracking, if custom tracking events are bound.
+        if ($(this).is('.colorbox')) {
+          // Do nothing here. The custom event will handle all tracking.
+          console.info("Click on .colorbox item has been detected.");
+        }
+        // Is download tracking activated and the file extension configured for download tracking?
+        else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) {
+          // Download link clicked.
+          console.info("Download url '%s' has been found. Tracked download as extension '%s'.", Drupal.googleanalytics.getPageUrl(this.href), Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase());
+          ga("send", "event", "Downloads", Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), Drupal.googleanalytics.getPageUrl(this.href));
+        }
+        else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
+          // Keep the internal URL for Google Analytics website overlay intact.
+          console.info("Click on internal special link '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(this.href));
+          ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(this.href) });
+        }
+        else {
+          // e.g. anchor in same page or other internal page link
+          console.info("Click on internal link '%s' detected, but not tracked by click.", this.href);
+        }
+      }
+      else {
+        if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
+          // Mailto link clicked.
+          console.info("Click on e-mail '%s' has been tracked.", this.href.substring(7));
+          ga("send", "event", "Mails", "Click", this.href.substring(7));
+        }
+        else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
+          if (Drupal.settings.googleanalytics.trackDomainMode != 2 || (Drupal.settings.googleanalytics.trackDomainMode == 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) {
+            // External link clicked / No top-level cross domain clicked.
+            console.info("Outbound link '%s' has been tracked.", this.href);
+            ga("send", "event", "Outbound links", "Click", this.href);
+          }
+          else {
+            console.info("Internal link '%s' clicked, not tracked.", this.href);
+          }
+        }
+      }
+    });
+
+    console.groupEnd();
+  });
+
+  // Track hash changes as unique pageviews, if this option has been enabled.
+  if (Drupal.settings.googleanalytics.trackUrlFragments) {
+    window.onhashchange = function() {
+      console.info("Track URL '%s' as pageview. Hash '%s' has changed.", location.pathname + location.search + location.hash, location.hash);
+      ga('send', 'pageview', location.pathname + location.search + location.hash);
+    }
+  }
+
+  // Colorbox: This event triggers when the transition has completed and the
+  // newly loaded content has been revealed.
+  $(document).bind("cbox_complete", function () {
+    var href = $.colorbox.element().attr("href");
+    if (href) {
+      console.info("Colorbox transition to url '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(href));
+      ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(href) });
+    }
+  });
+
+});
+
+/**
+ * Check whether the hostname is part of the cross domains or not.
+ *
+ * @param string hostname
+ *   The hostname of the clicked URL.
+ * @param array crossDomains
+ *   All cross domain hostnames as JS array.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) {
+  /**
+   * jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is
+   * `null` or `undefined`, http://bugs.jquery.com/ticket/10076,
+   * https://github.com/jquery/jquery/commit/a839af034db2bd934e4d4fa6758a3fed8de74174
+   *
+   * @todo: Remove/Refactor in D8
+   */
+  if (!crossDomains) {
+    return false;
+  }
+  else {
+    return $.inArray(hostname, crossDomains) > -1 ? true : false;
+  }
+};
+
+/**
+ * Check whether this is a download URL or not.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isDownload = function (url) {
+  var isDownload = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
+  return isDownload.test(url);
+};
+
+/**
+ * Check whether this is an absolute internal URL or not.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isInternal = function (url) {
+  var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i");
+  return isInternal.test(url);
+};
+
+/**
+ * Check whether this is a special URL or not.
+ *
+ * URL types:
+ *  - gotwo.module /go/* links.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isInternalSpecial = function (url) {
+  var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
+  return isInternalSpecial.test(url);
+};
+
+/**
+ * Extract the relative internal URL from an absolute internal URL.
+ *
+ * Examples:
+ * - http://mydomain.com/node/1 -> /node/1
+ * - http://example.com/foo/bar -> http://example.com/foo/bar
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return string
+ *   Internal website URL
+ */
+Drupal.googleanalytics.getPageUrl = function (url) {
+  var extractInternalUrl = new RegExp("^(https?):\/\/" + window.location.host, "i");
+  return url.replace(extractInternalUrl, '');
+};
+
+/**
+ * Extract the download file extension from the URL.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return string
+ *   The file extension of the passed url. e.g. "zip", "txt"
+ */
+Drupal.googleanalytics.getDownloadExtension = function (url) {
+  var extractDownloadextension = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
+  var extension = extractDownloadextension.exec(url);
+  return (extension === null) ? '' : extension[1];
+};
+
+})(jQuery);

+ 4 - 4
sites/all/modules/contrib/admin/google_analytics/googleanalytics.info

@@ -4,10 +4,10 @@ core = 7.x
 package = Statistics
 configure = admin/config/system/googleanalytics
 files[] = googleanalytics.test
-
-; Information added by drupal.org packaging script on 2012-11-01
-version = "7.x-1.3"
+test_dependencies[] = token
+; Information added by Drupal.org packaging script on 2014-11-29
+version = "7.x-2.1"
 core = "7.x"
 project = "google_analytics"
-datestamp = "1351810914"
+datestamp = "1417276982"
 

+ 126 - 23
sites/all/modules/contrib/admin/google_analytics/googleanalytics.install

@@ -5,33 +5,21 @@
  * Installation file for Google Analytics module.
  */
 
-/**
- * Implements hook_install().
- */
-function googleanalytics_install() {
-  // By German laws it's always best to enable the anonymizing of IP addresses.
-  // NOTE: If this is also an important default setting in other countries, please let us know!
-  $countries = array(
-    'DE',
-  );
-  if (in_array(variable_get('site_default_country', ''), $countries)) {
-    variable_set('googleanalytics_tracker_anonymizeip', 1);
-  }
-}
-
 /**
  * Implements hook_uninstall().
  */
 function googleanalytics_uninstall() {
   variable_del('googleanalytics_account');
   variable_del('googleanalytics_cache');
+  variable_del('googleanalytics_codesnippet_create');
   variable_del('googleanalytics_codesnippet_before');
   variable_del('googleanalytics_codesnippet_after');
   variable_del('googleanalytics_cross_domains');
   variable_del('googleanalytics_custom');
-  variable_del('googleanalytics_custom_var');
+  variable_del('googleanalytics_custom_dimension');
+  variable_del('googleanalytics_custom_metric');
+  variable_del('googleanalytics_debug');
   variable_del('googleanalytics_domain_mode');
-  variable_del('googleanalytics_js_scope');
   variable_del('googleanalytics_last_cache');
   variable_del('googleanalytics_pages');
   variable_del('googleanalytics_roles');
@@ -41,16 +29,20 @@ function googleanalytics_uninstall() {
   variable_del('googleanalytics_tracker_anonymizeip');
   variable_del('googleanalytics_trackfiles');
   variable_del('googleanalytics_trackfiles_extensions');
+  variable_del('googleanalytics_tracklinkid');
+  variable_del('googleanalytics_trackurlfragments');
+  variable_del('googleanalytics_trackuserid');
   variable_del('googleanalytics_trackmailto');
+  variable_del('googleanalytics_trackmessages');
   variable_del('googleanalytics_trackoutbound');
   variable_del('googleanalytics_translation_set');
   variable_del('googleanalytics_visibility_pages');
   variable_del('googleanalytics_visibility_roles');
+  variable_del('googleanalytics_privacy_donottrack');
 
   // Remove backup variables if exist. Remove this code in D8.
-  variable_del('googleanalytics_codesnippet_after_backup_6300');
-  variable_del('googleanalytics_codesnippet_before_backup_6300');
-  variable_del('googleanalytics_segmentation');
+  variable_del('googleanalytics_codesnippet_after_backup_7200');
+  variable_del('googleanalytics_codesnippet_before_backup_7200');
 }
 
 /**
@@ -71,14 +63,24 @@ function googleanalytics_requirements($phase) {
 
   if ($phase == 'runtime') {
     // Raise warning if Google user account has not been set yet.
-    if (!preg_match('/^UA-\d{4,}-\d+$/', variable_get('googleanalytics_account', 'UA-'))) {
-      $requirements['googleanalytics'] = array(
+    if (!preg_match('/^UA-\d+-\d+$/', variable_get('googleanalytics_account', 'UA-'))) {
+      $requirements['googleanalytics_account'] = array(
         'title' => $t('Google Analytics module'),
         'description' => $t('Google Analytics module has not been configured yet. Please configure its settings from the <a href="@url">Google Analytics settings page</a>.', array('@url' => url('admin/config/system/googleanalytics'))),
         'severity' => REQUIREMENT_WARNING,
         'value' => $t('Not configured'),
       );
     }
+
+    // Raise warning if debugging is enabled.
+    if (variable_get('googleanalytics_debug', 0)) {
+      $requirements['google_analytics_debugging'] = array(
+        'title' => $t('Google Analytics module'),
+        'description' => $t('Google Analytics module has debugging enabled. Please disable debugging setting in production sites from the <a href="@url">Google Analytics settings page</a>.', array('@url' => url('admin/config/system/googleanalytics'))),
+        'severity' => REQUIREMENT_WARNING,
+        'value' => $t('Debugging enabled'),
+      );
+    }
   }
 
   return $requirements;
@@ -425,10 +427,111 @@ function googleanalytics_update_7006() {
 }
 
 /**
-* Delete obsolete googleanalytics_trackpageloadtime variable.
-*/
+ * Delete obsolete googleanalytics_trackpageloadtime variable.
+ */
 function googleanalytics_update_7007() {
   variable_del('googleanalytics_trackpageloadtime');
 
   return t('Deleted obsolete googleanalytics_trackpageloadtime variable.');
 }
+
+/**
+ * Delete custom ga.js code snipptes to prevent malfunctions in new Universal Analytics tracker. A backup of your snippets will be created.
+ */
+function googleanalytics_update_7200() {
+  $messages = array();
+
+  // ga.js code will cause the tracker to break. Remove custom code snippets.
+  $googleanalytics_codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
+  if (!empty($googleanalytics_codesnippet_before) && stristr($googleanalytics_codesnippet_before, '_gaq.push(')) {
+    variable_set('googleanalytics_codesnippet_before_backup_7200', $googleanalytics_codesnippet_before);
+    variable_del('googleanalytics_codesnippet_before');
+    drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet."), 'warning');
+    $messages[] = t('Manual upgrade of custom "before" code snippet is required.');
+  }
+
+  $googleanalytics_codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
+  if (!empty($googleanalytics_codesnippet_after) && stristr($googleanalytics_codesnippet_after, '_gaq.push(')) {
+    variable_set('googleanalytics_codesnippet_after_backup_7200', $googleanalytics_codesnippet_after);
+    variable_del('googleanalytics_codesnippet_after');
+    drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet."), 'warning');
+    $messages[] = t('Manual upgrade of custom "after" code snippet is required.');
+  }
+
+  return empty($messages) ? t('No custom code snipped found. Nothing to do.') : implode(' ', $messages);
+}
+
+/**
+ * Delete obsolete custom variables. Custom variables are now custom dimensions and metrics.
+ */
+function googleanalytics_update_7201() {
+  variable_del('googleanalytics_custom_var');
+
+  return t('Deleted obsolete custom variables. Custom variables are now custom dimensions and metrics and you need to manually configure them!');
+}
+
+/**
+ * Delete obsolete JavaScript scope variable.
+ */
+function googleanalytics_update_7202() {
+  // Remove obsolete scope variable
+  variable_del('googleanalytics_js_scope');
+
+  return t('Removed obsolete JavaScript scope variable.');
+}
+
+/**
+ * Flatten the metrics and dimensions arrays.
+ */
+function googleanalytics_update_7203() {
+  $googleanalytics_custom_dimension = variable_get('googleanalytics_custom_dimension', array());
+  if (isset($googleanalytics_custom_dimension['indexes'])) {
+    foreach ($googleanalytics_custom_dimension['indexes'] as $dimension) {
+      $googleanalytics_custom_dimension['indexes'][$dimension['index']]['value'] = trim($dimension['value']);
+      // Remove empty values from the array.
+      if (!drupal_strlen($googleanalytics_custom_dimension['indexes'][$dimension['index']]['value'])) {
+        unset($googleanalytics_custom_dimension['indexes'][$dimension['index']]);
+      }
+    }
+    variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension['indexes']);
+  }
+  $googleanalytics_custom_metric = variable_get('googleanalytics_custom_metric', array());
+  if (isset($googleanalytics_custom_metric['indexes'])) {
+    foreach ($googleanalytics_custom_metric['indexes'] as $dimension) {
+      $googleanalytics_custom_metric['indexes'][$dimension['index']]['value'] = trim($dimension['value']);
+      // Remove empty values from the array.
+      if (!drupal_strlen($googleanalytics_custom_metric['indexes'][$dimension['index']]['value'])) {
+        unset($googleanalytics_custom_metric['indexes'][$dimension['index']]);
+      }
+    }
+    variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric['indexes']);
+  }
+
+  return t('Saved custom dimensions and metrics.');
+}
+
+/**
+ * Remove obsolete backup variables.
+ */
+function googleanalytics_update_7204() {
+  variable_del('googleanalytics_segmentation');
+  variable_del('googleanalytics_codesnippet_after_backup_6300');
+  variable_del('googleanalytics_codesnippet_before_backup_6300');
+  variable_del('googleanalytics_codesnippet_after_backup_6400');
+  variable_del('googleanalytics_codesnippet_before_backup_6400');
+
+  return t('Removed obsolete backup variables.');
+}
+
+/**
+ * Update list of default file extensions.
+ */
+function googleanalytics_update_7205() {
+  if (variable_get('googleanalytics_trackfiles_extensions', '') == '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls|xml|z|zip') {
+    variable_set('googleanalytics_trackfiles_extensions', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip');
+    return t('The default extensions for download tracking have been updated.');
+  }
+  else {
+    return t('Custom extensions for download tracking setting found. Update skipped!');
+  }
+}

+ 103 - 31
sites/all/modules/contrib/admin/google_analytics/googleanalytics.js

@@ -1,64 +1,61 @@
 (function ($) {
 
+Drupal.googleanalytics = {};
+
 $(document).ready(function() {
 
-  // Expression to check for absolute internal links.
-  var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i");
+  // Attach mousedown, keyup, touchstart events to document only and catch
+  // clicks on all elements.
+  $(document.body).bind("mousedown keyup touchstart", function(event) {
 
-  // Attach onclick event to document only and catch clicks on all elements.
-  $(document.body).click(function(event) {
     // Catch the closest surrounding link of a clicked element.
     $(event.target).closest("a,area").each(function() {
 
-      var ga = Drupal.settings.googleanalytics;
-      // Expression to check for special links like gotwo.module /go/* links.
-      var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
-      // Expression to check for download links.
-      var isDownload = new RegExp("\\.(" + ga.trackDownloadExtensions + ")$", "i");
-
       // Is the clicked URL internal?
-      if (isInternal.test(this.href)) {
+      if (Drupal.googleanalytics.isInternal(this.href)) {
         // Skip 'click' tracking, if custom tracking events are bound.
         if ($(this).is('.colorbox')) {
           // Do nothing here. The custom event will handle all tracking.
+          //console.info("Click on .colorbox item has been detected.");
         }
         // Is download tracking activated and the file extension configured for download tracking?
-        else if (ga.trackDownload && isDownload.test(this.href)) {
+        else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) {
           // Download link clicked.
-          var extension = isDownload.exec(this.href);
-          _gaq.push(["_trackEvent", "Downloads", extension[1].toUpperCase(), this.href.replace(isInternal, '')]);
+          ga("send", "event", "Downloads", Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), Drupal.googleanalytics.getPageUrl(this.href));
         }
-        else if (isInternalSpecial.test(this.href)) {
+        else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
           // Keep the internal URL for Google Analytics website overlay intact.
-          _gaq.push(["_trackPageview", this.href.replace(isInternal, '')]);
+          ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(this.href) });
         }
       }
       else {
-        if (ga.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
+        if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
           // Mailto link clicked.
-          _gaq.push(["_trackEvent", "Mails", "Click", this.href.substring(7)]);
+          ga("send", "event", "Mails", "Click", this.href.substring(7));
         }
-        else if (ga.trackOutbound && this.href.match(/^\w+:\/\//i)) {
-          if (ga.trackDomainMode == 2 && isCrossDomain($(this).attr('hostname'), ga.trackCrossDomains)) {
-            // Top-level cross domain clicked. document.location is handled by _link internally.
-            event.preventDefault();
-            _gaq.push(["_link", this.href]);
-          }
-          else {
-            // External link clicked.
-            _gaq.push(["_trackEvent", "Outbound links", "Click", this.href]);
+        else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
+          if (Drupal.settings.googleanalytics.trackDomainMode != 2 || (Drupal.settings.googleanalytics.trackDomainMode == 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains))) {
+            // External link clicked / No top-level cross domain clicked.
+            ga("send", "event", "Outbound links", "Click", this.href);
           }
         }
       }
     });
   });
 
+  // Track hash changes as unique pageviews, if this option has been enabled.
+  if (Drupal.settings.googleanalytics.trackUrlFragments) {
+    window.onhashchange = function() {
+      ga('send', 'pageview', location.pathname + location.search + location.hash);
+    }
+  }
+
   // Colorbox: This event triggers when the transition has completed and the
   // newly loaded content has been revealed.
-  $(document).bind("cbox_complete", function() {
+  $(document).bind("cbox_complete", function () {
     var href = $.colorbox.element().attr("href");
     if (href) {
-      _gaq.push(["_trackPageview", href.replace(isInternal, '')]);
+      ga("send", "pageview", { "page": Drupal.googleanalytics.getPageUrl(href) });
     }
   });
 
@@ -74,7 +71,7 @@ $(document).ready(function() {
  *
  * @return boolean
  */
-function isCrossDomain(hostname, crossDomains) {
+Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) {
   /**
    * jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is
    * `null` or `undefined`, http://bugs.jquery.com/ticket/10076,
@@ -88,6 +85,81 @@ function isCrossDomain(hostname, crossDomains) {
   else {
     return $.inArray(hostname, crossDomains) > -1 ? true : false;
   }
-}
+};
+
+/**
+ * Check whether this is a download URL or not.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isDownload = function (url) {
+  var isDownload = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
+  return isDownload.test(url);
+};
+
+/**
+ * Check whether this is an absolute internal URL or not.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isInternal = function (url) {
+  var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i");
+  return isInternal.test(url);
+};
+
+/**
+ * Check whether this is a special URL or not.
+ *
+ * URL types:
+ *  - gotwo.module /go/* links.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return boolean
+ */
+Drupal.googleanalytics.isInternalSpecial = function (url) {
+  var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
+  return isInternalSpecial.test(url);
+};
+
+/**
+ * Extract the relative internal URL from an absolute internal URL.
+ *
+ * Examples:
+ * - http://mydomain.com/node/1 -> /node/1
+ * - http://example.com/foo/bar -> http://example.com/foo/bar
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return string
+ *   Internal website URL
+ */
+Drupal.googleanalytics.getPageUrl = function (url) {
+  var extractInternalUrl = new RegExp("^(https?):\/\/" + window.location.host, "i");
+  return url.replace(extractInternalUrl, '');
+};
+
+/**
+ * Extract the download file extension from the URL.
+ *
+ * @param string url
+ *   The web url to check.
+ *
+ * @return string
+ *   The file extension of the passed url. e.g. "zip", "txt"
+ */
+Drupal.googleanalytics.getDownloadExtension = function (url) {
+  var extractDownloadextension = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
+  var extension = extractDownloadextension.exec(url);
+  return (extension === null) ? '' : extension[1];
+};
 
 })(jQuery);

+ 174 - 120
sites/all/modules/contrib/admin/google_analytics/googleanalytics.module

@@ -1,19 +1,35 @@
 <?php
 
-/*
+/**
  * @file
- * Drupal Module: GoogleAnalytics
- * Adds the required Javascript to the bottom of all your Drupal pages
- * to allow tracking by the Google Analytics statistics package.
+ * Drupal Module: Google Analytics
+ *
+ * Adds the required Javascript to all your Drupal pages to allow tracking by
+ * the Google Analytics statistics package.
  *
  * @author: Alexander Hass <http://drupal.org/user/85918>
  */
 
-define('GOOGLEANALYTICS_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls|xml|z|zip');
+/**
+ * Define the default file extension list that should be tracked as download.
+ */
+define('GOOGLEANALYTICS_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip');
 
-// Remove tracking from all administrative pages, see http://drupal.org/node/34970.
+/**
+ * Define default path exclusion list to remove tracking from admin pages,
+ * see http://drupal.org/node/34970 for more information.
+ */
 define('GOOGLEANALYTICS_PAGES', "admin\nadmin/*\nbatch\nnode/add*\nnode/*/*\nuser/*/*");
 
+/**
+ * Advertise the supported google analytics api details.
+ */
+function googleanalytics_api() {
+  return array(
+    'api' => 'analytics.js',
+  );
+}
+
 /**
  * Implements hook_help().
  */
@@ -88,19 +104,14 @@ function googleanalytics_page_alter(&$page) {
     '404 Not Found',
   );
 
-  // 1. Check if the GA account number has a value.
+  // 1. Check if the GA account number has a valid value.
   // 2. Track page views based on visibility value.
   // 3. Check if we should track the currently active user's role.
   // 4. Ignore pages visibility filter for 404 or 403 status codes.
-  if (!empty($id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) {
+  if (preg_match('/^UA-\d+-\d+$/', $id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) {
 
-    // We allow different scopes. Default to 'header' but allow user to override if they really need to.
-    $scope = variable_get('googleanalytics_js_scope', 'header');
-
-    if (variable_get('googleanalytics_trackadsense', FALSE)) {
-      // Custom tracking. Prepend before all other JavaScript.
-      drupal_add_js('window.google_analytics_uacct = ' . drupal_json_encode($id) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1));
-    }
+    $debug = variable_get('googleanalytics_debug', 0);
+    $url_custom = '';
 
     // Add link tracking.
     $link_settings = array();
@@ -120,10 +131,23 @@ function googleanalytics_page_alter(&$page) {
     if ($track_cross_domains = variable_get('googleanalytics_cross_domains', '')) {
       $link_settings['trackCrossDomains'] = preg_split('/(\r\n?|\n)/', $track_cross_domains);
     }
+    if ($track_url_fragments = variable_get('googleanalytics_trackurlfragments', 0)) {
+      $link_settings['trackUrlFragments'] = $track_url_fragments;
+      $url_custom = 'location.pathname + location.search + location.hash';
+    }
 
     if (!empty($link_settings)) {
       drupal_add_js(array('googleanalytics' => $link_settings), 'setting');
-      drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.js');
+
+      // Add debugging code.
+      if ($debug) {
+        drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.debug.js');
+        // Add the JS test in development to the page.
+        //drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.test.js');
+      }
+      else {
+        drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.js');
+      }
     }
 
     // Add messages tracking.
@@ -140,15 +164,17 @@ function googleanalytics_page_alter(&$page) {
         // Track only the selected message types.
         if (in_array($type, $message_types)) {
           foreach ($messages as $message) {
-            $message_events .= '_gaq.push(["_trackEvent", ' . drupal_json_encode(t('Messages')) . ', ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode(strip_tags($message)) . ']);';
+            // @todo: Track as exceptions?
+            $message_events .= 'ga("send", "event", ' . drupal_json_encode(t('Messages')) . ', ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode(strip_tags($message)) . ');';
           }
         }
       }
     }
 
     // Site search tracking support.
-    $url_custom = '';
     if (module_exists('search') && variable_get('googleanalytics_site_search', FALSE) && arg(0) == 'search' && $keys = googleanalytics_search_get_keys()) {
+      // hook_preprocess_search_results() is not executed if search result is
+      // empty. Make sure the counter is set to 0 if there are no results.
       $url_custom = '(window.googleanalytics_search_results) ? ' . drupal_json_encode(url('search/' . arg(1), array('query' => array('search' => $keys)))) . ' : ' . drupal_json_encode(url('search/' . arg(1), array('query' => array('search' => 'no-results:' . $keys, 'cat' => 'no-results'))));
     }
 
@@ -174,70 +200,134 @@ function googleanalytics_page_alter(&$page) {
       $url_custom = '"/404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
     }
 
-    // Add any custom code snippets if specified.
-    $codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
-    $codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
-
-    // Add custom variables.
-    $googleanalytics_custom_vars = variable_get('googleanalytics_custom_var', array());
+    // Add custom dimensions and metrics.
     $custom_var = '';
-    for ($i = 1; $i < 6; $i++) {
-      $custom_var_name = !empty($googleanalytics_custom_vars['slots'][$i]['name']) ? $googleanalytics_custom_vars['slots'][$i]['name'] : '';
-      if (!empty($custom_var_name)) {
-        $custom_var_value = !empty($googleanalytics_custom_vars['slots'][$i]['value']) ? $googleanalytics_custom_vars['slots'][$i]['value'] : '';
-        $custom_var_scope = !empty($googleanalytics_custom_vars['slots'][$i]['scope']) ? $googleanalytics_custom_vars['slots'][$i]['scope'] : 3;
-
-        $types = array();
-        $node = menu_get_object();
-        if (is_object($node)) {
-          $types += array('node' => $node);
-        }
-        $custom_var_name = token_replace($custom_var_name, $types, array('clear' => TRUE));
-        $custom_var_value = token_replace($custom_var_value, $types, array('clear' => TRUE));
+    foreach (array('dimension', 'metric') as $googleanalytics_custom_type) {
+      $googleanalytics_custom_vars = variable_get('googleanalytics_custom_' . $googleanalytics_custom_type, array());
+      // Are there dimensions or metrics configured?
+      if (!empty($googleanalytics_custom_vars)) {
+        // Add all the configured variables to the content.
+        foreach ($googleanalytics_custom_vars as $googleanalytics_custom_var) {
+          // Replace tokens in values.
+          $types = array();
+          $node = menu_get_object();
+          if (is_object($node)) {
+            $types += array('node' => $node);
+          }
+          $googleanalytics_custom_var['value'] = token_replace($googleanalytics_custom_var['value'], $types, array('clear' => TRUE));
 
-        // Suppress empty custom names and/or variables.
-        if (!drupal_strlen(trim($custom_var_name)) || !drupal_strlen(trim($custom_var_value))) {
-          continue;
-        }
+          // Suppress empty values.
+          if (!drupal_strlen(trim($googleanalytics_custom_var['value']))) {
+            continue;
+          }
 
-        // The length of the string used for the 'name' and the length of the
-        // string used for the 'value' must not exceed 128 bytes after url encoding.
-        $name_length = drupal_strlen(rawurlencode($custom_var_name));
-        $tmp_value = rawurlencode($custom_var_value);
-        $value_length = drupal_strlen($tmp_value);
-        if ($name_length + $value_length > 128) {
-          // Trim value and remove fragments of url encoding.
-          $tmp_value = rtrim(substr($tmp_value, 0, 127 - $name_length), '%0..9A..F');
-          $custom_var_value = urldecode($tmp_value);
-        }
+          // Per documentation the max length of a dimension is 150 bytes.
+          // A metric has no length limitation. It's not documented if this
+          // limit means 150 bytes after url encoding or before.
+          // See https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#customs
+          if ($googleanalytics_custom_type == 'dimension' && drupal_strlen($googleanalytics_custom_var['value']) > 150) {
+            $googleanalytics_custom_var['value'] = substr($googleanalytics_custom_var['value'], 0, 150);
+          }
 
-        $custom_var_name = drupal_json_encode($custom_var_name);
-        $custom_var_value = drupal_json_encode($custom_var_value);
-        $custom_var .= "_gaq.push(['_setCustomVar', $i, $custom_var_name, $custom_var_value, $custom_var_scope]);";
+          // Cast metric values for json_encode to data type numeric.
+          if ($googleanalytics_custom_type == 'metric') {
+            settype($googleanalytics_custom_var['value'], 'float');
+          };
+
+          // Add variables to tracker.
+          $custom_var .= 'ga("set", ' . drupal_json_encode($googleanalytics_custom_type . $googleanalytics_custom_var['index']) . ', ' . drupal_json_encode($googleanalytics_custom_var['value']) . ');';
+        }
       }
     }
 
     // Build tracker code.
-    $script = 'var _gaq = _gaq || [];';
-    $script .= '_gaq.push(["_setAccount", ' . drupal_json_encode($id) . ']);';
-    if (variable_get('googleanalytics_tracker_anonymizeip', 0)) {
-      // FIXME: The Google API is currently broken and "_gat._anonymizeIp" is only
-      // a workaround until "_anonymizeIp" has been implemented/fixed.
-      $script .= '_gaq.push(["_gat._anonymizeIp"]);';
+    $script = '(function(i,s,o,g,r,a,m){';
+    $script .= 'i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){';
+    $script .= '(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),';
+    $script .= 'm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)';
+    $script .= '})(window,document,"script",';
+
+    // Which version of the tracking library should be used?
+    $library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js');
+    $library_cache_url = 'http:' . $library_tracker_url;
+
+    // Should a local cached copy of analytics.js be used?
+    if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_cache_url)) {
+      // A dummy query-string is added to filenames, to gain control over
+      // browser-caching. The string changes on every update or full cache
+      // flush, forcing browsers to load a new copy of the files, as the
+      // URL changed.
+      $query_string = '?' . variable_get('css_js_query_string', '0');
+
+      $script .= '"' . $url . $query_string . '"';
+    }
+    else {
+      $script .= '"' . $library_tracker_url . '"';
     }
+    $script .= ',"ga");';
+
+    // Add any custom code snippets if specified.
+    $codesnippet_create = variable_get('googleanalytics_codesnippet_create', array());
+    $codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
+    $codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
+
+    // Build the create only fields list.
+    $create_only_fields = array('cookieDomain' => 'auto');
+    $create_only_fields = array_merge($create_only_fields, $codesnippet_create);
 
     // Domain tracking type.
     global $cookie_domain;
     $domain_mode = variable_get('googleanalytics_domain_mode', 0);
+    $googleanalytics_adsense_script = '';
 
     // Per RFC 2109, cookie domains must contain at least one dot other than the
     // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
     if ($domain_mode == 1 && count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
-      $script .= '_gaq.push(["_setDomainName", ' . drupal_json_encode($cookie_domain) . ']);';
+      $create_only_fields = array_merge($create_only_fields, array('cookieDomain' => $cookie_domain));
+      $googleanalytics_adsense_script .= 'window.google_analytics_domain_name = ' . drupal_json_encode($cookie_domain) . ';';
     }
     elseif ($domain_mode == 2) {
-      $script .= '_gaq.push(["_setDomainName", "none"]);';
-      $script .= '_gaq.push(["_setAllowLinker", true]);';
+      // Cross Domain tracking. 'autoLinker' need to be enabled in 'create'.
+      $create_only_fields = array_merge($create_only_fields, array('allowLinker' => TRUE));
+      $googleanalytics_adsense_script .= 'window.google_analytics_domain_name = "none";';
+    }
+
+    // Track logged in users across all devices.
+    if (variable_get('googleanalytics_trackuserid', 0) && user_is_logged_in()) {
+      // The USER_ID value should be a unique, persistent, and non-personally
+      // identifiable string identifier that represents a user or signed-in
+      // account across devices.
+      $create_only_fields['userId'] = drupal_hmac_base64($user->uid, drupal_get_private_key() . drupal_get_hash_salt());
+    }
+
+    // Create a tracker.
+    $script .= 'ga("create", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($create_only_fields) .');';
+
+    // Prepare Adsense tracking.
+    $googleanalytics_adsense_script .= 'window.google_analytics_uacct = ' . drupal_json_encode($id) . ';';
+
+    // Add enhanced link attribution after 'create', but before 'pageview' send.
+    // @see https://support.google.com/analytics/answer/2558867
+    if (variable_get('googleanalytics_tracklinkid', 0)) {
+      $script .= 'ga("require", "linkid", "linkid.js");';
+    }
+
+    // Add display features after 'create', but before 'pageview' send.
+    // @see https://support.google.com/analytics/answer/2444872
+    if (variable_get('googleanalytics_trackdoubleclick', 0)) {
+      $script .= 'ga("require", "displayfeatures");';
+    }
+
+    // Domain tracking type.
+    if ($domain_mode == 2) {
+      // Cross Domain tracking
+      // https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#cross-domain
+      $script .= 'ga("require", "linker");';
+      $script .= 'ga("linker:autoLink", ' . drupal_json_encode($link_settings['trackCrossDomains']) . ');';
+    }
+
+    if (variable_get('googleanalytics_tracker_anonymizeip', 1)) {
+      $script .= 'ga("set", "anonymizeIp", true);';
     }
 
     if (!empty($custom_var)) {
@@ -246,12 +336,11 @@ function googleanalytics_page_alter(&$page) {
     if (!empty($codesnippet_before)) {
       $script .= $codesnippet_before;
     }
-    if (empty($url_custom)) {
-      $script .= '_gaq.push(["_trackPageview"]);';
-    }
-    else {
-      $script .= '_gaq.push(["_trackPageview", ' . $url_custom . ']);';
+    if (!empty($url_custom)) {
+      $script .= 'ga("set", "page", ' . $url_custom . ');';
     }
+    $script .= 'ga("send", "pageview");';
+
     if (!empty($message_events)) {
       $script .= $message_events;
     }
@@ -259,45 +348,14 @@ function googleanalytics_page_alter(&$page) {
       $script .= $codesnippet_after;
     }
 
-    $script .= '(function() {';
-    $script .= 'var ga = document.createElement("script");';
-    $script .= 'ga.type = "text/javascript";';
-    $script .= 'ga.async = true;';
-
-    // Which version of the tracking library should be used?
-    if ($trackdoubleclick = variable_get('googleanalytics_trackdoubleclick', FALSE)) {
-      $library_tracker_url = 'stats.g.doubleclick.net/dc.js';
-      $library_cache_url = 'http://' . $library_tracker_url;
-    }
-    else {
-      $library_tracker_url = '.google-analytics.com/ga.js';
-      $library_cache_url = 'http://www' . $library_tracker_url;
-    }
-
-    // Should a local cached copy of ga.js be used?
-    if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_cache_url)) {
-      // A dummy query-string is added to filenames, to gain control over
-      // browser-caching. The string changes on every update or full cache
-      // flush, forcing browsers to load a new copy of the files, as the
-      // URL changed.
-      $query_string = '?' . variable_get('css_js_query_string', '0');
-
-      $script .= 'ga.src = "' . $url . $query_string . '";';
-    }
-    else {
-      // Library paths do not follow the same naming convention.
-      if ($trackdoubleclick) {
-        $script .= 'ga.src = ("https:" == document.location.protocol ? "https://" : "http://") + "' . $library_tracker_url . '";';
-      }
-      else {
-        $script .= 'ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + "' . $library_tracker_url . '";';
-      }
+    if (variable_get('googleanalytics_trackadsense', FALSE)) {
+      // Custom tracking. Prepend before all other JavaScript.
+      // @TODO: https://support.google.com/adsense/answer/98142
+      // sounds like it could be appended to $script.
+      drupal_add_js($googleanalytics_adsense_script, array('type' => 'inline', 'group' => JS_LIBRARY-1));
     }
-    $script .= 'var s = document.getElementsByTagName("script")[0];';
-    $script .= 's.parentNode.insertBefore(ga, s);';
-    $script .= '})();';
 
-    drupal_add_js($script, array('scope' => $scope, 'type' => 'inline'));
+    drupal_add_js($script, array('scope' => 'header', 'type' => 'inline'));
   }
 }
 
@@ -382,13 +440,7 @@ function googleanalytics_user_presave(&$edit, $account, $category) {
 function googleanalytics_cron() {
   // Regenerate the tracking code file every day.
   if (REQUEST_TIME - variable_get('googleanalytics_last_cache', 0) >= 86400 && variable_get('googleanalytics_cache', 0)) {
-    // Which version of the tracking library should be used?
-    if (variable_get('googleanalytics_trackdoubleclick', FALSE)) {
-      _googleanalytics_cache('http://stats.g.doubleclick.net/dc.js', TRUE);
-    }
-    else {
-      _googleanalytics_cache('http://www.google-analytics.com/ga.js', TRUE);
-    }
+    _googleanalytics_cache('http://www.google-analytics.com/analytics.js', TRUE);
     variable_set('googleanalytics_last_cache', REQUEST_TIME);
   }
 }
@@ -399,11 +451,13 @@ function googleanalytics_cron() {
  * Collects and adds the number of search results to the head.
  */
 function googleanalytics_preprocess_search_results(&$variables) {
-  // There is no search result $variable available that hold the number of items
-  // found. But the pager item mumber can tell the number of search results.
-  global $pager_total_items;
+  if (variable_get('googleanalytics_site_search', FALSE)) {
+    // There is no search result $variable available that hold the number of items
+    // found. But the pager item mumber can tell the number of search results.
+    global $pager_total_items;
 
-  drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1));
+    drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1));
+  }
 }
 
 /**
@@ -428,16 +482,16 @@ function googleanalytics_search_get_keys() {
  *
  * @param $location
  *   The full URL to the external javascript file.
- * @param $sync_cached_file
- *   Synchronize tracking code and update if remote file have changed.
+ * @param $synchronize
+ *   Synchronize to local cache if remote file has changed.
  * @return mixed
  *   The path to the local javascript file on success, boolean FALSE on failure.
  */
-function _googleanalytics_cache($location, $sync_cached_file = FALSE) {
+function _googleanalytics_cache($location, $synchronize = FALSE) {
   $path = 'public://googleanalytics';
   $file_destination = $path . '/' . basename($location);
 
-  if (!file_exists($file_destination) || $sync_cached_file) {
+  if (!file_exists($file_destination) || $synchronize) {
     // Download the latest tracking code.
     $result = drupal_http_request($location);
 

+ 411 - 132
sites/all/modules/contrib/admin/google_analytics/googleanalytics.test

@@ -8,8 +8,8 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
 
   public static function getInfo() {
     return array(
-      'name' => t('Google Analytics basic tests'),
-      'description' => t('Test basic functionality of Google Analytics module.'),
+      'name' => 'Google Analytics basic tests',
+      'description' => 'Test basic functionality of Google Analytics module.',
       'group' => 'Google Analytics',
     );
   }
@@ -20,6 +20,8 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $permissions = array(
       'access administration pages',
       'administer google analytics',
+      'administer modules',
+      'administer site configuration',
     );
 
     // User to set up google_analytics.
@@ -28,23 +30,38 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
   }
 
   function testGoogleAnalyticsConfiguration() {
+    // Check if Configure link is available on 'Modules' page.
+    // Requires 'administer modules' permission.
+    $this->drupalGet('admin/modules');
+    $this->assertRaw('admin/config/system/googleanalytics', '[testGoogleAnalyticsConfiguration]: Configure link from Modules page to Google Analytics Settings page exists.');
+
+    // Check if Configure link is available on 'Status Reports' page. NOTE: Link is only shown without UA code configured.
+    // Requires 'administer site configuration' permission.
+    $this->drupalGet('admin/reports/status');
+    $this->assertRaw('admin/config/system/googleanalytics', '[testGoogleAnalyticsConfiguration]: Configure link from Status Reports page to Google Analytics Settings page exists.');
+
     // Check for setting page's presence.
     $this->drupalGet('admin/config/system/googleanalytics');
     $this->assertRaw(t('Web Property ID'), '[testGoogleAnalyticsConfiguration]: Settings page displayed.');
 
     // Check for account code validation.
     $edit['googleanalytics_account'] = $this->randomName(2);
-    $this->drupalPost('admin/config/system/googleanalytics', $edit, 'Save configuration');
+    $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
     $this->assertRaw(t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.'), '[testGoogleAnalyticsConfiguration]: Invalid Web Property ID number validated.');
   }
 
   function testGoogleAnalyticsPageVisibility() {
+    // Verify that no tracking code is embedded into the webpage; if there is
+    // only the module installed, but UA code not configured. See #2246991.
+    $this->drupalGet('');
+    $this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed without UA code configured.');
+
     $ua_code = 'UA-123456-1';
     variable_set('googleanalytics_account', $ua_code);
 
     // Show tracking on "every page except the listed pages".
     variable_set('googleanalytics_visibility_pages', 0);
-    // Disable tracking one "admin*" pages only.
+    // Disable tracking on "admin*" pages only.
     variable_set('googleanalytics_pages', "admin\nadmin/*");
     // Enable tracking only for authenticated users only.
     variable_set('googleanalytics_roles', array(DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID));
@@ -58,7 +75,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin page.');
     $this->drupalGet('admin/config/system/googleanalytics');
     // Checking for tracking code URI here, as $ua_code is displayed in the form.
-    $this->assertNoRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.');
+    $this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.');
 
     // Test whether tracking code display is properly flipped.
     variable_set('googleanalytics_visibility_pages', 1);
@@ -66,7 +83,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $this->assertRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin page.');
     $this->drupalGet('admin/config/system/googleanalytics');
     // Checking for tracking code URI here, as $ua_code is displayed in the form.
-    $this->assertRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.');
+    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.');
     $this->drupalGet('');
     $this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is NOT displayed on front page.');
 
@@ -89,22 +106,22 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     $this->assertRaw('/404.html', '[testGoogleAnalyticsPageVisibility]: 404 Not Found tracking code shown on non-existent page.');
 
     // DNT Tests:
-    // Enable caching of pages for anonymous users.
+    // Enable system internal page cache for anonymous users.
     variable_set('cache', 1);
     // Test whether DNT headers will fail to disable embedding of tracking code.
     $this->drupalGet('', array(), array('DNT: 1'));
-    $this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT header send from client, but page caching is enabled and tracker cannot removed.');
-    // DNT works only with caching of pages for anonymous users disabled.
+    $this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT header send from client, but page caching is enabled and tracker cannot removed.');
+    // DNT works only with system internal page cache for anonymous users disabled.
     variable_set('cache', 0);
     $this->drupalGet('');
-    $this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: Tracking is enabled without DNT header.');
+    $this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: Tracking is enabled without DNT header.');
     // Test whether DNT header is able to remove the tracking code.
     $this->drupalGet('', array(), array('DNT: 1'));
-    $this->assertNoRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT header received from client. Tracking has been disabled by browser.');
+    $this->assertNoRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT header received from client. Tracking has been disabled by browser.');
     // Disable DNT feature and see if tracker is still embedded.
     variable_set('googleanalytics_privacy_donottrack', 0);
     $this->drupalGet('', array(), array('DNT: 1'));
-    $this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT feature is disabled, DNT header from browser has been ignored.');
+    $this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT feature is disabled, DNT header from browser has been ignored.');
   }
 
   function testGoogleAnalyticsTrackingCode() {
@@ -118,40 +135,75 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
 
     /* Sample JS code as added to page:
     <script type="text/javascript" src="/sites/all/modules/google_analytics/googleanalytics.js?w"></script>
-    <script type="text/javascript">
-      var _gaq = _gaq || [];
-      _gaq.push(['_setAccount', 'UA-123456-7']);
-      _gaq.push(['_trackPageview']);
-
-      (function() {
-        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
-        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-      })();
+    <script>
+    (function(q,u,i,c,k){window['GoogleAnalyticsObject']=q;
+    window[q]=window[q]||function(){(window[q].q=window[q].q||[]).push(arguments)},
+    window[q].l=1*new Date();c=i.createElement(u),k=i.getElementsByTagName(u)[0];
+    c.async=true;c.src='//www.google-analytics.com/analytics.js';
+    k.parentNode.insertBefore(c,k)})('ga','script',document);
+    ga('create', 'UA-123456-7');
+    ga('send', 'pageview');
     </script>
+    <!-- End Google Analytics -->
     */
 
     // Test whether tracking code uses latest JS.
     variable_set('googleanalytics_cache', 0);
     $this->drupalGet('');
-    $this->assertRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.');
-
-    // Test whether the alternate doubleclick library is used
-    variable_set('googleanalytics_trackdoubleclick', 1);
-    $this->drupalGet('');
-    $this->assertRaw('stats.g.doubleclick.net/dc.js', '[testGoogleAnalyticsTrackingCode]: Doubleclick tracking code used.');
+    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.');
 
     // Test whether anonymize visitors IP address feature has been enabled.
+    variable_set('googleanalytics_tracker_anonymizeip', 0);
     $this->drupalGet('');
-    $this->assertNoRaw('_gaq.push(["_gat._anonymizeIp"]);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address not found on frontpage.');
+    $this->assertNoRaw('ga("set", "anonymizeIp", true);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address not found on frontpage.');
     // Enable anonymizing of IP addresses.
     variable_set('googleanalytics_tracker_anonymizeip', 1);
     $this->drupalGet('');
-    $this->assertRaw('_gaq.push(["_gat._anonymizeIp"]);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address found on frontpage.');
+    $this->assertRaw('ga("set", "anonymizeIp", true);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address found on frontpage.');
+
+    // Test if track Enhanced Link Attribution is enabled.
+    variable_set('googleanalytics_tracklinkid', 1);
+    $this->drupalGet('');
+    $this->assertRaw('ga("require", "linkid", "linkid.js");', '[testGoogleAnalyticsTrackingCode]: Tracking code for Enhanced Link Attribution is enabled.');
+
+    // Test if track Enhanced Link Attribution is disabled.
+    variable_set('googleanalytics_tracklinkid', 0);
+    $this->drupalGet('');
+    $this->assertNoRaw('ga("require", "linkid", "linkid.js");', '[testGoogleAnalyticsTrackingCode]: Tracking code for Enhanced Link Attribution is not enabled.');
+
+    // Test if tracking of User ID is enabled.
+    variable_set('googleanalytics_trackuserid', 1);
+    $this->drupalGet('');
+    $this->assertRaw(', {"cookieDomain":"auto","userId":"', '[testGoogleAnalyticsTrackingCode]: Tracking code for User ID is enabled.');
+
+    // Test if tracking of User ID is disabled.
+    variable_set('googleanalytics_trackuserid', 0);
+    $this->drupalGet('');
+    $this->assertNoRaw(', {"cookieDomain":"auto","userId":"', '[testGoogleAnalyticsTrackingCode]: Tracking code for User ID is disabled.');
+
+    // Test if tracking of url fragments is enabled.
+    variable_set('googleanalytics_trackurlfragments', 1);
+    $this->drupalGet('');
+    $this->assertRaw('ga("set", "page", location.pathname + location.search + location.hash);', '[testGoogleAnalyticsTrackingCode]: Tracking code for url fragments is enabled.');
+
+    // Test if tracking of url fragments is disabled.
+    variable_set('googleanalytics_trackurlfragments', 0);
+    $this->drupalGet('');
+    $this->assertNoRaw('ga("set", "page", location.pathname + location.search + location.hash);', '[testGoogleAnalyticsTrackingCode]: Tracking code for url fragments is not enabled.');
+
+    // Test if track display features is enabled.
+    variable_set('googleanalytics_trackdoubleclick', 1);
+    $this->drupalGet('');
+    $this->assertRaw('ga("require", "displayfeatures");', '[testGoogleAnalyticsTrackingCode]: Tracking code for display features is enabled.');
+
+    // Test if track display features is disabled.
+    variable_set('googleanalytics_trackdoubleclick', 0);
+    $this->drupalGet('');
+    $this->assertNoRaw('ga("require", "displayfeatures");', '[testGoogleAnalyticsTrackingCode]: Tracking code for display features is not enabled.');
 
     // Test whether single domain tracking is active.
     $this->drupalGet('');
-    $this->assertNoRaw('_gaq.push(["_setDomainName"', '[testGoogleAnalyticsTrackingCode]: Single domain tracking is active.');
+    $this->assertRaw('{"cookieDomain":"auto"}', '[testGoogleAnalyticsTrackingCode]: Single domain tracking is active.');
 
     // Enable "One domain with multiple subdomains".
     variable_set('googleanalytics_domain_mode', 1);
@@ -161,36 +213,62 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
     // TODO: Workaround to run tests successfully. This feature cannot tested reliable.
     global $cookie_domain;
     if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
-      $this->assertRaw('_gaq.push(["_setDomainName",', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains is active on real host.');
+      $this->assertRaw('{"cookieDomain":"' . $cookie_domain . '"}', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains is active on real host.');
     }
     else {
       // Special cases, Localhost and IP addresses don't show '_setDomainName'.
-      $this->assertNoRaw('_gaq.push(["_setDomainName",', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).');
+      $this->assertNoRaw('{"cookieDomain":"' . $cookie_domain . '"}', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).');
     }
 
     // Enable "Multiple top-level domains" tracking.
     variable_set('googleanalytics_domain_mode', 2);
     variable_set('googleanalytics_cross_domains', "www.example.com\nwww.example.net");
     $this->drupalGet('');
-    $this->assertRaw('_gaq.push(["_setDomainName", "none"]);', '[testGoogleAnalyticsTrackingCode]: _setDomainName: "none" found. Cross domain tracking is active.');
-    $this->assertRaw('_gaq.push(["_setAllowLinker", true]);', '[testGoogleAnalyticsTrackingCode]: _setAllowLinker: true found. Cross domain tracking is active.');
+    $this->assertRaw('ga("create", "' . $ua_code . '", {"cookieDomain":"auto","allowLinker":true', '[testGoogleAnalyticsTrackingCode]: "allowLinker" has been found. Cross domain tracking is active.');
+    $this->assertRaw('ga("require", "linker");', '[testGoogleAnalyticsTrackingCode]: Require linker has been found. Cross domain tracking is active.');
+    $this->assertRaw('ga("linker:autoLink", ["www.example.com","www.example.net"]);', '[testGoogleAnalyticsTrackingCode]: "linker:autoLink" has been found. Cross domain tracking is active.');
     $this->assertRaw('"trackCrossDomains":["www.example.com","www.example.net"]', '[testGoogleAnalyticsTrackingCode]: Cross domain tracking with www.example.com and www.example.net is active.');
+    variable_set('googleanalytics_domain_mode', 0);
+
+    // Test whether debugging script has been enabled.
+    variable_set('googleanalytics_debug', 1);
+    $this->drupalGet('');
+    $this->assertRaw('//www.google-analytics.com/analytics_debug.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been enabled.');
+
+    // Check if text and link is shown on 'Status Reports' page.
+    // Requires 'administer site configuration' permission.
+    $this->drupalGet('admin/reports/status');
+    $this->assertRaw(t('Google Analytics module has debugging enabled. Please disable debugging setting in production sites from the <a href="@url">Google Analytics settings page</a>.', array('@url' => url('admin/config/system/googleanalytics'))), '[testGoogleAnalyticsConfiguration]: Debugging enabled is shown on Status Reports page.');
 
-    // Test whether the BEFORE and AFTER code is added to the tracker.
-    variable_set('googleanalytics_codesnippet_before', '_setDetectFlash(false);');
-    variable_set('googleanalytics_codesnippet_after', '_gaq.push(["t2._setAccount", "UA-123456-3"]);_gaq.push(["t2._trackPageview"]);');
+    // Test whether debugging script has been disabled.
+    variable_set('googleanalytics_debug', 0);
     $this->drupalGet('');
-    $this->assertRaw('_setDetectFlash(false);', '[testGoogleAnalyticsTrackingCode]: Before codesnippet has been found with "Flash" detection disabled.');
-    $this->assertRaw('t2._setAccount', '[testGoogleAnalyticsTrackingCode]: After codesnippet with "t2" tracker has been found.');
+    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been disabled.');
+
+    // Test whether the CREATE and BEFORE and AFTER code is added to the tracker.
+    $codesnippet_create = array(
+      'cookieDomain' => 'foo.example.com',
+      'cookieName' => 'myNewName',
+      'cookieExpires' => 20000,
+      'allowAnchor' => TRUE,
+      'sampleRate' => 4.3,
+    );
+    variable_set('googleanalytics_codesnippet_create', $codesnippet_create);
+    variable_set('googleanalytics_codesnippet_before', 'ga("set", "forceSSL", true);');
+    variable_set('googleanalytics_codesnippet_after', 'ga("create", "UA-123456-3", {"name": "newTracker"});ga("newTracker.send", "pageview");');
+    $this->drupalGet('');
+    $this->assertRaw('ga("create", "' . $ua_code . '", {"cookieDomain":"foo.example.com","cookieName":"myNewName","cookieExpires":20000,"allowAnchor":true,"sampleRate":4.3});', '[testGoogleAnalyticsTrackingCode]: Create only fields have been found.');
+    $this->assertRaw('ga("set", "forceSSL", true);', '[testGoogleAnalyticsTrackingCode]: Before codesnippet will force http pages to also send all beacons using https.');
+    $this->assertRaw('ga("create", "UA-123456-3", {"name": "newTracker"});', '[testGoogleAnalyticsTrackingCode]: After codesnippet with "newTracker" tracker has been found.');
   }
 }
 
-class GoogleAnalyticsCustomVariablesTest extends DrupalWebTestCase {
+class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase {
 
   public static function getInfo() {
     return array(
-      'name' => t('Google Analytics Custom Variables tests'),
-      'description' => t('Test custom variables functionality of Google Analytics module.'),
+      'name' => 'Google Analytics custom dimensions and metrics tests',
+      'description' => 'Test custom dimensions and metrics functionality of Google Analytics module.',
       'group' => 'Google Analytics',
       'dependencies' => array('token'),
     );
@@ -208,99 +286,140 @@ class GoogleAnalyticsCustomVariablesTest extends DrupalWebTestCase {
     $this->admin_user = $this->drupalCreateUser($permissions);
   }
 
-  function testGoogleAnalyticsCustomVariables() {
+  function testGoogleAnalyticsCustomDimensions() {
     $ua_code = 'UA-123456-3';
     variable_set('googleanalytics_account', $ua_code);
 
     // Basic test if the feature works.
-    $custom_vars = array(
-      'slots' => array(
-        1 => array(
-          'slot' => 1,
-          'name' => 'Foo 1',
-          'value' => 'Bar 1',
-          'scope' => 3,
-        ),
-        2 => array(
-          'slot' => 2,
-          'name' => 'Foo 2',
-          'value' => 'Bar 2',
-          'scope' => 2,
-        ),
-        3 => array(
-          'slot' => 3,
-          'name' => 'Foo 3',
-          'value' => 'Bar 3',
-          'scope' => 3,
-        ),
-        4 => array(
-          'slot' => 4,
-          'name' => 'Foo 4',
-          'value' => 'Bar 4',
-          'scope' => 2,
-        ),
-        5 => array(
-          'slot' => 5,
-          'name' => 'Foo 5',
-          'value' => 'Bar 5',
-          'scope' => 1,
-        ),
-      )
+    $googleanalytics_custom_dimension = array(
+      1 => array(
+        'index' => 1,
+        'value' => 'Bar 1',
+      ),
+      2 => array(
+        'index' => 2,
+        'value' => 'Bar 2',
+      ),
+      3 => array(
+        'index' => 3,
+        'value' => 'Bar 3',
+      ),
+      4 => array(
+        'index' => 4,
+        'value' => 'Bar 4',
+      ),
+      5 => array(
+        'index' => 5,
+        'value' => 'Bar 5',
+      ),
     );
-    variable_set('googleanalytics_custom_var', $custom_vars);
+    variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension);
     $this->drupalGet('');
 
-    foreach ($custom_vars['slots'] as $slot) {
-      $this->assertRaw("_gaq.push(['_setCustomVar', " . $slot['slot'] . ", \"" . $slot['name'] . "\", \"" . $slot['value'] . "\", " . $slot['scope'] . "]);", '[testGoogleAnalyticsCustomVariables]: _setCustomVar ' . $slot['slot'] . ' is shown.');
+    foreach ($googleanalytics_custom_dimension as $dimension) {
+      $this->assertRaw('ga("set", ' . drupal_json_encode('dimension' . $dimension['index']) . ', ' . drupal_json_encode($dimension['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Dimension #' . $dimension['index'] . ' is shown.');
     }
 
-    // Test whether tokens are replaced in custom variable names.
+    // Test whether tokens are replaced in custom dimension values.
     $site_slogan = $this->randomName(16);
     variable_set('site_slogan', $site_slogan);
 
-    $custom_vars = array(
-      'slots' => array(
-        1 => array(
-          'slot' => 1,
-          'name' => 'Name: [site:slogan]',
-          'value' => 'Value: [site:slogan]',
-          'scope' => 3,
-        ),
-        2 => array(
-          'slot' => 2,
-          'name' => '',
-          'value' => $this->randomName(16),
-          'scope' => 1,
-        ),
-        3 => array(
-          'slot' => 3,
-          'name' => $this->randomName(16),
-          'value' => '',
-          'scope' => 2,
-        ),
-        4 => array(
-          'slot' => 4,
-          'name' => '',
-          'value' => '',
-          'scope' => 3,
-        ),
-        5 => array(
-          'slot' => 5,
-          'name' => '',
-          'value' => '',
-          'scope' => 3,
-        ),
-      )
+    $googleanalytics_custom_dimension = array(
+      1 => array(
+        'index' => 1,
+        'value' => 'Value: [site:slogan]',
+      ),
+      2 => array(
+        'index' => 2,
+        'value' => $this->randomName(16),
+      ),
+      3 => array(
+        'index' => 3,
+        'value' => '',
+      ),
+      // #2300701: Custom dimensions and custom metrics not outputed on zero value.
+      4 => array(
+        'index' => 4,
+        'value' => '0',
+      ),
+    );
+    variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension);
+    $this->verbose('<pre>' . print_r($googleanalytics_custom_dimension, TRUE) . '</pre>');
+
+    $this->drupalGet('');
+    $this->assertRaw('ga("set", ' . drupal_json_encode('dimension1') . ', ' . drupal_json_encode("Value: $site_slogan") . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Tokens have been replaced in dimension value.');
+    $this->assertRaw('ga("set", ' . drupal_json_encode('dimension2') . ', ' . drupal_json_encode($googleanalytics_custom_dimension['2']['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Random value is shown.');
+    $this->assertNoRaw('ga("set", ' . drupal_json_encode('dimension3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.');
+    $this->assertRaw('ga("set", ' . drupal_json_encode('dimension4') . ', ' . drupal_json_encode('0') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Value 0 is shown.');
+  }
+
+  function testGoogleAnalyticsCustomMetrics() {
+    $ua_code = 'UA-123456-3';
+    variable_set('googleanalytics_account', $ua_code);
+
+    // Basic test if the feature works.
+    $googleanalytics_custom_metric = array(
+      1 => array(
+        'index' => 1,
+        'value' => '6',
+        'value_expected' => 6,
+      ),
+      2 => array(
+        'index' => 2,
+        'value' => '8000',
+        'value_expected' => 8000,
+      ),
+      3 => array(
+        'index' => 3,
+        'value' => '7.8654',
+        'value_expected' => 7.8654,
+      ),
+      4 => array(
+        'index' => 4,
+        'value' => '1123.4',
+        'value_expected' => 1123.4,
+      ),
+      5 => array(
+        'index' => 5,
+        'value' => '5,67',
+        'value_expected' => 5,
+      ),
     );
-    variable_set('googleanalytics_custom_var', $custom_vars);
-    $this->verbose('<pre>' . print_r($custom_vars, TRUE) . '</pre>');
+    variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric);
+    $this->drupalGet('');
+
+    foreach ($googleanalytics_custom_metric as $metric) {
+      $this->assertRaw('ga("set", ' . drupal_json_encode('metric' . $metric['index']) . ', ' . drupal_json_encode($metric['value_expected']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Metric #' . $metric['index'] . ' is shown.');
+    }
+
+    // Test whether tokens are replaced in custom metric values.
+    $googleanalytics_custom_metric = array(
+      1 => array(
+        'index' => 1,
+        'value' => '[current-user:roles:count]',
+      ),
+      2 => array(
+        'index' => 2,
+        'value' => mt_rand(),
+      ),
+      3 => array(
+        'index' => 3,
+        'value' => '',
+      ),
+      // #2300701: Custom dimensions and custom metrics not outputed on zero value.
+      4 => array(
+        'index' => 4,
+        'value' => '0',
+      ),
+    );
+    variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric);
+    $this->verbose('<pre>' . print_r($googleanalytics_custom_metric, TRUE) . '</pre>');
 
     $this->drupalGet('');
-    $this->assertRaw("_gaq.push(['_setCustomVar', 1, \"Name: $site_slogan\", \"Value: $site_slogan\", 3]", '[testGoogleAnalyticsCustomVariables]: Tokens have been replaced in custom variable.');
-    $this->assertNoRaw("_gaq.push(['_setCustomVar', 2,", '[testGoogleAnalyticsCustomVariables]: Value with empty name is not shown.');
-    $this->assertNoRaw("_gaq.push(['_setCustomVar', 3,", '[testGoogleAnalyticsCustomVariables]: Name with empty value is not shown.');
-    $this->assertNoRaw("_gaq.push(['_setCustomVar', 4,", '[testGoogleAnalyticsCustomVariables]: Empty name and value is not shown.');
-    $this->assertNoRaw("_gaq.push(['_setCustomVar', 5,", '[testGoogleAnalyticsCustomVariables]: Empty name and value is not shown.');
+    $this->assertRaw('ga("set", ' . drupal_json_encode('metric1') . ', ', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Tokens have been replaced in metric value.');
+    $this->assertRaw('ga("set", ' . drupal_json_encode('metric2') . ', ' . drupal_json_encode($googleanalytics_custom_metric['2']['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Random value is shown.');
+    $this->assertNoRaw('ga("set", ' . drupal_json_encode('metric3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.');
+    $this->assertRaw('ga("set", ' . drupal_json_encode('metric4') . ', ' . drupal_json_encode(0) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Value 0 is shown.');
   }
 }
 
@@ -308,8 +427,8 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
 
   public static function getInfo() {
     return array(
-      'name' => t('Google Analytics status messages tests'),
-      'description' => t('Test status messages functionality of Google Analytics module.'),
+      'name' => 'Google Analytics status messages tests',
+      'description' => 'Test status messages functionality of Google Analytics module.',
       'group' => 'Google Analytics',
     );
   }
@@ -333,9 +452,9 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
     // Enable logging of errors only.
     variable_set('googleanalytics_trackmessages', array('error' => 'error'));
 
-    $this->drupalPost('user/login', array(), 'Log in');
-    $this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Username field is required."]);', '[testGoogleAnalyticsStatusMessages]: _trackEvent "Username field is required." is shown.');
-    $this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Password field is required."]);', '[testGoogleAnalyticsStatusMessages]: _trackEvent "Password field is required." is shown.');
+    $this->drupalPost('user/login', array(), t('Log in'));
+    $this->assertRaw('ga("send", "event", "Messages", "Error message", "Username field is required.");', '[testGoogleAnalyticsStatusMessages]: Event message "Username field is required." is shown.');
+    $this->assertRaw('ga("send", "event", "Messages", "Error message", "Password field is required.");', '[testGoogleAnalyticsStatusMessages]: Event message "Password field is required." is shown.');
 
     // @todo: investigate why drupal_set_message() fails.
     //drupal_set_message('Example status message.', 'status');
@@ -343,10 +462,10 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
     //drupal_set_message('Example error message.', 'error');
     //drupal_set_message('Example error <em>message</em> with html tags and <a href="http://example.com/">link</a>.', 'error');
     //$this->drupalGet('');
-    //$this->assertNoRaw('_gaq.push(["_trackEvent", "Messages", "Status message", "Example status message."]);', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.');
-    //$this->assertNoRaw('_gaq.push(["_trackEvent", "Messages", "Warning message", "Example warning message."]);', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.');
-    //$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Example error message."]);', '[testGoogleAnalyticsStatusMessages]: Example error message is shown.');
-    //$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testGoogleAnalyticsStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
+    //$this->assertNoRaw('ga("send", "event", "Messages", "Status message", "Example status message.");', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.');
+    //$this->assertNoRaw('ga("send", "event", "Messages", "Warning message", "Example warning message.");', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.');
+    //$this->assertRaw('ga("send", "event", "Messages", "Error message", "Example error message.");', '[testGoogleAnalyticsStatusMessages]: Example error message is shown.');
+    //$this->assertRaw('ga("send", "event", "Messages", "Error message", "Example error message with html tags and link.");', '[testGoogleAnalyticsStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
   }
 }
 
@@ -354,8 +473,8 @@ class GoogleAnalyticsRolesTest extends DrupalWebTestCase {
 
   public static function getInfo() {
     return array(
-      'name' => t('Google Analytics role tests'),
-      'description' => t('Test roles functionality of Google Analytics module.'),
+      'name' => 'Google Analytics role tests',
+      'description' => 'Test roles functionality of Google Analytics module.',
       'group' => 'Google Analytics',
     );
   }
@@ -438,5 +557,165 @@ class GoogleAnalyticsRolesTest extends DrupalWebTestCase {
     $this->drupalGet('');
     $this->assertRaw($ua_code, '[testGoogleAnalyticsRoleVisibility]: Tracking code is displayed on frontpage for included anonymous users.');
   }
+}
+
+class GoogleAnalyticsSearchTest extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Google Analytics search tests',
+      'description' => 'Test search functionality of Google Analytics module.',
+      'group' => 'Google Analytics',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('googleanalytics', 'search', 'node');
+
+    $permissions = array(
+      'access administration pages',
+      'administer google analytics',
+      'search content',
+      'create page content',
+      'edit own page content',
+    );
+
+    // User to set up google_analytics.
+    $this->admin_user = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($this->admin_user);
+  }
+
+  function testGoogleAnalyticsSearchTracking() {
+    $ua_code = 'UA-123456-1';
+    variable_set('googleanalytics_account', $ua_code);
+
+    // Check tracking code visibility.
+    $this->drupalGet('');
+    $this->assertRaw($ua_code, '[testGoogleAnalyticsSearch]: Tracking code is displayed for authenticated users.');
+
+    $this->drupalGet('search/node');
+    $this->assertNoRaw('ga("set", "page",', '[testGoogleAnalyticsSearch]: Custom url not set.');
+
+    // Enable site search support.
+    variable_set('googleanalytics_site_search', 1);
+
+    // Search for random string.
+    $search = array();
+    $search['keys'] = $this->randomName(8);
+
+    // Create a node to search for.
+    $langcode = LANGUAGE_NONE;
+    $edit = array();
+    $edit['title'] = 'This is a test title';
+    $edit["body[$langcode][0][value]"] = 'This test content contains ' . $search['keys'] . ' string.';
+
+    // Fire a search, it's expected to get 0 results.
+    $this->drupalPost('search/node', $search, t('Search'));
+    $this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.');
+    $this->assertRaw('window.googleanalytics_search_results = 0;', '[testGoogleAnalyticsSearch]: Search yielded no results.');
+
+    // Save the node.
+    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->assertText(t('@type @title has been created.', array('@type' => 'Basic page', '@title' => $edit['title'])), 'Node was created.');
+
+    // Index the node or it cannot found.
+    $this->cronRun();
+
+    $this->drupalPost('search/node', $search, t('Search'));
+    $this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.');
+    $this->assertRaw('window.googleanalytics_search_results = 1;', '[testGoogleAnalyticsSearch]: One search result found.');
+
+    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->assertText(t('@type @title has been created.', array('@type' => 'Basic page', '@title' => $edit['title'])), 'Node was created.');
+
+    // Index the node or it cannot found.
+    $this->cronRun();
+
+    $this->drupalPost('search/node', $search, t('Search'));
+    $this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.');
+    $this->assertRaw('window.googleanalytics_search_results = 2;', '[testGoogleAnalyticsSearch]: Two search results found.');
+  }
+
+}
+
+class GoogleAnalyticsPhpFilterTest extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Google Analytics php filter tests',
+      'description' => 'Test php filter functionality of Google Analytics module.',
+      'group' => 'Google Analytics',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('googleanalytics', 'php');
+
+    // Administrator with all permissions.
+    $permissions_admin_user = array(
+      'access administration pages',
+      'administer google analytics',
+      'use PHP for tracking visibility',
+    );
+    $this->admin_user = $this->drupalCreateUser($permissions_admin_user);
+
+    // Administrator who cannot configure tracking visibility with PHP.
+    $permissions_delegated_admin_user = array(
+      'access administration pages',
+      'administer google analytics',
+    );
+    $this->delegated_admin_user = $this->drupalCreateUser($permissions_delegated_admin_user);
+  }
+
+  function testGoogleAnalyticsPhpFilter() {
+    $ua_code = 'UA-123456-1';
+    $this->drupalLogin($this->admin_user);
+
+    $edit = array();
+    $edit['googleanalytics_account'] = $ua_code;
+    $edit['googleanalytics_visibility_pages'] = 2;
+    $edit['googleanalytics_pages'] = '<?php return 0; ?>';
+    $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
+
+    // Compare saved setting with posted setting.
+    $googleanalytics_pages = variable_get('googleanalytics_pages', $this->randomName(8));
+    $this->assertEqual('<?php return 0; ?>', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.');
+
+    // Check tracking code visibility.
+    variable_set('googleanalytics_pages', '<?php return TRUE; ?>');
+    $this->drupalGet('');
+    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on frontpage page.');
+    $this->drupalGet('admin');
+    $this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on admin page.');
+
+    variable_set('googleanalytics_pages', '<?php return FALSE; ?>');
+    $this->drupalGet('');
+    $this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is not displayed on frontpage page.');
+
+    // Test administration form.
+    variable_set('googleanalytics_pages', '<?php return TRUE; ?>');
+    $this->drupalGet('admin/config/system/googleanalytics');
+    $this->assertRaw(t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'), '[testGoogleAnalyticsPhpFilter]: Permission to administer PHP for tracking visibility.');
+    $this->assertRaw(check_plain('<?php return TRUE; ?>'), '[testGoogleAnalyticsPhpFilter]: PHP code snippted is displayed.');
+
+    // Login the delegated user and check if fields are visible.
+    $this->drupalLogin($this->delegated_admin_user);
+    $this->drupalGet('admin/config/system/googleanalytics');
+    $this->assertNoRaw(t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'), '[testGoogleAnalyticsPhpFilter]: No permission to administer PHP for tracking visibility.');
+    $this->assertNoRaw(check_plain('<?php return TRUE; ?>'), '[testGoogleAnalyticsPhpFilter]: No permission to view PHP code snippted.');
+
+    // Set a different value and verify that this is still the same after the post.
+    variable_set('googleanalytics_pages', '<?php return 0; ?>');
+
+    $edit = array();
+    $edit['googleanalytics_account'] = $ua_code;
+    $this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
+
+    // Compare saved setting with posted setting.
+    $googleanalytics_visibility_pages = variable_get('googleanalytics_visibility_pages', 0);
+    $googleanalytics_pages = variable_get('googleanalytics_pages', $this->randomName(8));
+    $this->assertEqual(2, $googleanalytics_visibility_pages, '[testGoogleAnalyticsPhpFilter]: Pages on which this PHP code returns TRUE is selected.');
+    $this->assertEqual('<?php return 0; ?>', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.');
+  }
 
 }

+ 119 - 0
sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js

@@ -0,0 +1,119 @@
+(function ($) {
+
+/**
+ *  This file is for developers only.
+ *
+ *  This tests are made for the javascript functions used in GA module.
+ *  These tests verify if the return values are properly working.
+ *
+ *  Hopefully this can be added somewhere else once Drupal core has JavaScript
+ *  unit testing integrated.
+ */
+
+"use strict";
+
+Drupal.googleanalytics.test = {};
+
+Drupal.googleanalytics.test.assertSame = function (value1, value2, message) {
+  if (value1 === value2) {
+    console.info(message);
+  }
+  else {
+    console.error(message);
+  }
+};
+
+Drupal.googleanalytics.test.assertNotSame = function (value1, value2, message) {
+  if (value1 !== value2) {
+    console.info(message);
+  }
+  else {
+    console.error(message);
+  }
+};
+
+Drupal.googleanalytics.test.assertTrue = function (value1, message) {
+  if (value1 === true) {
+    console.info(message);
+  }
+  else {
+    console.error(message);
+  }
+};
+
+Drupal.googleanalytics.test.assertFalse = function (value1, message) {
+  if (value1 === false) {
+    console.info(message);
+  }
+  else {
+    console.error(message);
+  }
+};
+
+// Run after the documented is ready or Drupal.settings is undefined.
+$(document).ready(function() {
+
+  /**
+   *  Run javascript tests against the GA module.
+   */
+
+  // JavaScript debugging
+  var base_url = window.location.protocol + '//' + window.location.host;
+  var base_path = window.location.pathname;
+  console.dir(Drupal);
+
+  console.group("Test 'isDownload':");
+  Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'node/8'), "Verify that '/node/8' url is not detected as file download.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip'), "Verify that '/files/foo1.zip' url is detected as a file download.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip#foo'), "Verify that '/files/foo1.zip#foo' url is detected as a file download.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip?foo=bar'), "Verify that '/files/foo1.zip?foo=bar' url is detected as a file download.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip?foo=bar#foo'), "Verify that '/files/foo1.zip?foo=bar#foo' url is detected as a file download.");
+  Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo2.ddd'), "Verify that '/files/foo2.ddd' url is not detected as file download.");
+  console.groupEnd();
+
+  console.group("Test 'isInternal':");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1'), "Link '" + base_url + Drupal.settings.basePath + "node/2' has been detected as internal link.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1#foo' has been detected as internal link.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar' has been detected as internal link.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar#foo' has been detected as internal link.");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as internal link.");
+  Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternal('http://example.com/node/3'), "Link 'http://example.com/node/3' has been detected as external link.");
+  console.groupEnd();
+
+  console.group("Test 'isInternalSpecial':");
+  Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternalSpecial(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as special internal link.");
+  Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternalSpecial(base_url + Drupal.settings.basePath + 'node/1'), "Link '" + base_url + Drupal.settings.basePath + "node/1' has been detected as special internal link.");
+  console.groupEnd();
+
+  console.group("Test 'getPageUrl':");
+  Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(base_url + Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" +  Drupal.settings.basePath + "node/1' has been extracted from full qualified url '" + base_url + base_path + "'.");
+  Drupal.googleanalytics.test.assertSame(base_path, Drupal.googleanalytics.getPageUrl(Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" +  Drupal.settings.basePath + "node/1' has been extracted from absolute url '" +  base_path + "'.");
+  Drupal.googleanalytics.test.assertSame('http://example.com/node/2', Drupal.googleanalytics.getPageUrl('http://example.com/node/2'), "Full qualified external url 'http://example.com/node/2' has been extracted.");
+  Drupal.googleanalytics.test.assertSame('//example.com/node/2', Drupal.googleanalytics.getPageUrl('//example.com/node/2'), "Full qualified external url '//example.com/node/2' has been extracted.");
+  console.groupEnd();
+
+  console.group("Test 'getDownloadExtension':");
+  Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip'.");
+  Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip#foo'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip#foo'.");
+  Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip?foo=bar'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip?foo=bar'.");
+  Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip?foo=bar#foo'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip?foo=bar'.");
+  Drupal.googleanalytics.test.assertSame('', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo2.dddd'), "No download extension found in '" + base_url + Drupal.settings.basePath + "files/foo2.dddd'.");
+  console.groupEnd();
+
+  // List of top-level domains: example.com, example.net
+  console.group("Test 'isCrossDomain' (requires cross domain configuration with 'example.com' and 'example.net'):");
+  if (Drupal.settings.googleanalytics.trackCrossDomains) {
+    console.dir(Drupal.settings.googleanalytics.trackCrossDomains);
+    Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list.");
+    Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list.");
+    Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list.");
+    Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list.");
+  }
+  else {
+    console.warn('Cross domain tracking is not enabled. Tests skipped.');
+  }
+  console.groupEnd();
+
+});
+
+})(jQuery);

+ 2 - 1
sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc

@@ -17,6 +17,7 @@ function googleanalytics_variable_info($options) {
     'required' => TRUE,
     'group' => 'googleanalytics',
     'localize' => TRUE,
+    'multidomain' => TRUE,
     'validate callback' => 'googleanalytics_validate_googleanalytics_account',
   );
 
@@ -44,7 +45,7 @@ function googleanalytics_validate_googleanalytics_account($variable) {
   // Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes.
   $variable['value'] = str_replace(array('–', '—', '−'), '-', $variable['value']);
 
-  if (!preg_match('/^UA-\d{4,}-\d+$/', $variable['value'])) {
+  if (!preg_match('/^UA-\d+-\d+$/', $variable['value'])) {
     return t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.');
   }
 }

+ 173 - 41
sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt

@@ -1,50 +1,182 @@
-Module Filter 7.x-1.8, 2013-08-08
+Module Filter 7.x-2.x, 2015-02-20
 ---------------------------------
-by greenSkin: Changed the title for the 'module_filter_dynamic_save_position'
-  checkbox to make it clearer what it does.
-#1933384 by kaidjohnson: Fixed jQuery UI button breaks functionality.
-#1166414 by greenSkin: Fixed module filter + tabs disabled = broken modules
-  page.
+Simplifying the table rows by hiding version and requirements until a
+  particular description is clicked.
 
 
-Module Filter 7.x-1.7, 2012-07-05
+Module Filter 7.x-2.x, 2014-09-01
 ---------------------------------
+#2235553 by greenSkin: Fixed latest jquery_update breaks module filter.
+#2304687 by mpdonadio: Fixed Remove hardcoded operations.
+#2293029 by topsitemakers: Fixed Take header offset into account when selecting
+  a tab.
+#2290213 by topsitemakers: Minor typo in description - "has no affect" -> "has
+  no effect".
+
+
+Module Filter 7.x-2.0-alpha2, 2013-12-06
+----------------------------------------
+#2141743, #2141743 by greenSkin: Fixed issues related to the new dynamically
+  positioned tabs and using the dynamically positioned save button.
+
+
+Module Filter 7.x-2.0-alpha1, 2013-11-18
+----------------------------------------
+by greenSkin: Tabs now should always be visible while scrolling large lists of
+  modules.
+#1854348 by alexweber, greenSkin: Make filter textfield wider on modules page
+  when viewing as tabs.
+by greenSkin: Fixed what was suppose to be a call to variable_set().
+#1370492 by greenSkin: Remember selected tab after modules form submit.
+by greenSkin: Changed the title for the 'module_filter_dynamic_save_position'
+  checkbox to make it clearer what it does.
+#1166414 by greenSkin: Fixed broken submit when tabs are disabled. We no longer
+  reroute the theme of the system modules page unless tabs are enabled.
+by greenSkin: Fixed issue on Available updates page where the update state
+  would not be remembered.
+by greenSkin: Fixed 7200 update to rebuild the theme registry at the same time
+  as rebuilding the menu.
+by greenSkin: Fixed issue relating to row coloring when enabling/disabling
+  modules via switch due to recent fix for jQuery Update module.
+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.
+by greenSkin: Added description to filter textfield on permissions page.
+by greenSkin: Added filter to user permissions page.
+by greenSkin: Fixed styling when JavaScript is disabled.
+by greenSkin: Fixed compatiblility with page_actions.
+#1351184 klonos, greenSkin: Added support for update_advanced "Ignored from
+  settings".
+#1149978 by good_man, greenSkin: Added RTL Styling for tabs and toggle switch.
+by greenSkin: Hide toggle switch when JavaScript is disabled.
+by greenSkin: Added support for ctools dropbutton as well as views styling for
+  ctools dropbutton.
+by greenSkin: Improved hash validation.
+by greenSkin: Added ability for tabs to be disabled. Direct use case is when
+  the "New" tab contains zero new modules.
+by greenSkin: Added title to "New" tab link that helps to describe the criteria
+  of a "new" module.
+#1320796 by greenSkin: Added some validation checks before trying to select a
+  tab in case the tab does not actually exist.
+#1429248 by klonos, greenSkin: Fixed Modules page table header overlaps
+  admin_menu().
+#1494694 by greenSkin: Added Let me decide if I want the cursor to focus on the
+  search box or not.
+#1515256 by catmat, greenSkin: Fixed Tabbed theme may remove functional
+  content.
+by greenSkin: Integrated dynamic positioning of the save button back in, but
+  if the module page_actions is enabled we let it handle the save button. See
+  issue #1424994.
+by greenSkin: Clicking on module name now affects the toggle switch.
+by greenSkin: Do not render the switch for incompatible modules.
 #1033012 by greenSkin: Hide incompatible module rows when the 'Unavailable'
   checkbox is unchecked.
-#1170388 by greenSkin: Fixed conflict with Overlay module. Added class
-  "overlay-exclude" to tab links.
+by greenSkin: Performance tweak to counting the number of enabled modules.
+by greenSkin: Base new modules on the filectime of their .info file.
+#1424034 by greenSkin: Now adds the jquery.cookie.js file when needed.
+by greenSkin: Removed tweaks to the save configuration button in favor
+  http://drupal.org/project/page_actions.
+by greenSkin: Updated hook_uninstall to del all Module Filter variables.
+by greenSkin: No longer including machine name in the tab summary of modules to
+  enable/disable.
+by greenSkin: Made switches honor disabled checkboxes. A disabled switch can
+  not be turned on or off.
+by greenSkin: Added setting to toggle between using the switch or checkbox for
+  enabling/disabling modules.
+by greenSkin: Centered enable switch.
+by greenSkin: Moved couple styles to be applied via JavaScript instead of CSS
+  so they get applied once the initial loading has finished.
+by greenSkin: Implemented a "switch" look instead of checkboxes.
+by greenSkin: Remember last selected state on "Available updates" page.
+by greenSkin: Switched to using filectime() rather than filemtime() for
+  determining new modules.
+#1354134 by klonos, greenSkin: Module list now formats correctly in core
+  "Garland" theme.
+#1354134 by klonos, greenSkin: Module list now formats correctly in core
+  "Garland" theme.
 #1124218 by jyee, greenSkin: Suppress form submission when hitting the enter
   key while the filter input is focused.
-
-
-Module Filter 7.x-1.6, 2011-09-15
----------------------------------
-#1241662 by Niklas Fiekas: Sort modules by display name.
+by greenSkin: Simplified filter rules for updates page. Instead of checkboxes,
+  now using radios.
+by greenSkin: Fixed "All" tab to be selected by default when the page is first
+  loaded.
+#1350124 by greenSkin: Fixed filtering on package tab.
+by greenSkin: Added the "All" tab back. Added a "New" tab that lists modules
+  installed within the last week.
+by greenSkin: Fixed "No Results" not showing within selected tabs.
+#1259876 by greenSkin: Filter criteria remembered after save.
+by greenSkin: Remove deprecated function moduleGetID() from JavaScript code.
+by greenSkin: Filter now uses OR instead of AND when filtering multiply
+  queries.
+#1288590 by greenSkin: Fixed Drupal.settings.moduleFilter.enabledCounts[id] is
+  undefined.
+#1344214 by eMPee584: Fixed Notice: Undefined index: #default_value in
+  theme_module_filter_system_modules_tabs().
+#1170388 by greenSkin: Fixed confict with Overlay module. Added class
+  "overlay-exclude" to tab links. Added setting to toggle the use of a URL
+  fragment when selecting tabs.
+by greenSkin: Added README.txt.
+by greenSkin: Fixed table row striping.
+by greenSkin: Fixed regular expression to not require an operator be at the
+  beginning. Make filter queries filter with AND instead of OR. Each query will
+  further filter the list rather than potentially add to it.
+by greenSkin: Spruced up the regular expression for splitting query strings.
+by greenSkin: Added "description:" operator.
+by greenSkin: Added operator support to filter. Added "requires:" and
+  "requiredBy:" operators.
+by greenSkin: Filter now processes multiple queries separated by spaces. Use
+  quotes for a single query that includes space(s).
+by greenSkin: Fixed not updating the index when a module is enabled/disabled.
+by greenSkin: Fixed visual aid for enabling/disabling modules. Previously had
+  failed to remove +/- from tab summary.
+by Kiphaas7, greenSkin: Tabs can now be configured to hide when they contain
+  no results.
+by Kiphaas7, greenSkin: Added result info to tabs. When a filter is performed,
+  a count per tab is displayed of the number of visible results for that tab.
+by greenSkin: Now more descriptive of what modules are being enabled/disabled.
+by greenSkin: Moved module operation links to below "Requires" and
+  "Required by" section.
+by greenSkin: Added a suggest class to tabs when their module is hovered.
+by greenSkin: Distinguished difference between tab ID and hash.
+by greenSkin: Only alter the menu item 'admin/reports/updates' if it first
+  exists.
+by greenSkin: Added update to force a menu rebuild. This is needed to let
+  Module Filter alter the Update Status menu item in order to provide our
+  filter on its page.
 #1254140 by greenSkin: No longer return anything in hook_update_7100.
-by greenSkin: Fixed bug with visual aids sometimes not updating correctly.
-
-
-Module Filter 7.x-1.5, 2011-08-16
----------------------------------
-by greenSkin: Brought the 7.x branch current with the 6.x branch features.
-
-
-Module Filter 7.x-1.3, 2011-03-07
----------------------------------
-by realityloop: Updated CHANGELOG.txt
-
-
-Module Filter 7.x-1.2, 2011-03-07
----------------------------------
-by realityloop: Changed placement of Submit buttons for other languages
-
-
-Module Filter 7.x-1.1, 2011-03-07
----------------------------------
-by realityloop: first commit via git, broke the release somehow :/
-
-
-Module Filter 7.x-1.0, 2011-01-04
----------------------------------
-by realityloop: Fixed Undefined index error.
-by greenSkin: Removed unused .css and .js files.
+#1257860 by greenSkin: Added filter to update status report.
+by greenSkin: Moved hiding of the inputs wrapper to css rather than a style
+  attribute.
+by greenSkin: Added missing semi-colons in JavaScript.
+by greenSkin: Changed hook comment from using "Implementation of" to
+  "Implements".
+by greenSkin: Fixed showing all modules by default when no hash is present.
+by greenSkin: Turned off autocomplete for filter textfield.
+by greenSkin: Now using "all" for hash when no tab is selected.
+by greenSkin: Implemented visual aids (displays number of modules being
+  enabled/disabled as well as coloring the modules row accordingly).
+by greenSkin: Implemented the enabled count (displays the number of enabled
+  modules of total for a package.
+by greenSkin: Updated tabs setting description.
+by greenSkin: Added missing period.
+by greenSkin: Made dynamic save position default to on. Updated element title
+  on admin page and removed "DEVELOPMENTAL" from description.
+by greenSkin: Improved fixed-top positioning when toolbar is enabled.
+by greenSkin: Added fixed classes for submit button wrapper.
+by greenSkin: Changed module-filter-tabs from a class to an id.
+by greenSkin: Fixed filter on modules page when tabs are disabled.
+by greenSkin: Set min-height to #module-filter-modules.
+by greenSkin: Implemented module_filter element and using attached js and css
+  more.
+by greenSkin: Filter input and checkboxes can now have their default values set
+  based on query params.
+by greenSkin: Tabs now use URL fragments.
+by greenSkin: Fixed regular expression used to determine tab ID.
+by greenSkin: Tabs have been re-written and are functioning.
+by greenSkin: Added Module Filter to "Administration" package.
+by greenSkin: Improved filtering performance.
+by greenSkin: New filter code.
+by greenSkin: Initial tab layout modified. Modules are all in one table but
+  look like they are in packages. All JavaScript has to do on load now is
+  remove the package name and header rows from tbody then sort the rows.
+by greenSkin: Modified the menu item's description.

+ 107 - 0
sites/all/modules/contrib/admin/module_filter/README.txt

@@ -0,0 +1,107 @@
+Description
+-----------
+This module provides a method for filtering modules on the modules page as well
+as for filtering projects on the update status report.
+
+The supplied filter is simpler than using your browsers find feature which
+searches the entire page. The provided filter will filter modules/projects that
+do not meet your input.
+
+Along with the filter textfield there are additional
+checkboxes that help to narrow the search more. The modules page contains four
+checkboxes: Enabled, Disabled, Required, and Unavailable. While the first two
+are self-explanatory, the latter two can take an explanation. The Required
+checkbox affects visibility of modules that are enabled and have other
+module(s) that require it also enabled. The Unavailable checkbox affects
+visibility of modules that are disabled and depend on module(s) that are
+missing.
+
+The update status report filter also contains four checkboxes: Up-to-Date,
+Update availabe, Security update, and Unknown. These directly affect the
+visibilty of each project; whether it is up-to-date, there is an update
+available, a security update is available, or the status is unknown.
+
+Installation
+------------
+To install this module, do the following:
+
+1. Extract the tar ball that you downloaded from Drupal.org.
+
+2. Upload the entire directory and all its contents to your modules directory.
+
+Configuration
+-------------
+To enable and configure this module do the following:
+
+1. Go to Admin -> Modules, and enable Module Filter.
+
+2. Go to Admin -> Configuration -> User interface -> Module filter, and make
+   any necessary configuration changes. 
+
+Tabs
+----
+By default Module Filter alters the modules page into tabs (Can be disabled on
+configuration page). In the tabs view, each package is converted to a vertical
+tab rather than a fieldset which greatly increases the ability to browse them.
+
+There are several benefits to using the tabs view over the standard view for
+the modules page. I've listed the key benefits below as well as additional
+information that pertains to each.
+
+1.  The increased ease of browsing between packages.
+
+2.  Allows all modules to be listed alphabetically outside of their package,
+    making it all the easier to find the module by name rather than package it
+    happens to be in.
+
+3.  The operations for a module are moved within the description column giving
+    the description more "elbow room".
+
+4.  Filtering is restricted to within the active tab or globally when no tab is
+    selected. By default no tab is selected which will list all modules. When a
+    tab is active and you want to get back to the 'all' state click on the
+    active tab to deselect it.
+
+5.  The number of enabled modules per tab is shown on the active tab. (Can be
+    disabled on configuration page)
+
+6.  Nice visual aids become available showing what modules are to be
+    enabled/disabled and the number of matching modules in each tab when
+    filtering. (Can be disabled on configuration page)
+
+7.  The save configuration button becomes more accessible, either staying at
+    the bottom of the window when the tabs exceed past the bottom and at the
+    top when scrolling past the tabs. (Can be disabled on configuration page)
+
+8.  When filtering, tabs that do not contain matches can be hidden. (Can be
+    enabled on configuration page)
+
+9.  Tab states are remembered like individual pages allowing you to move
+    forward and backward within your selections via your browsers
+    forward/backward buttons.
+
+10. When viewing all modules (no active tab) and mousing over modules it's tab
+    becomes highlighted to signify which tab it belongs to.
+
+Filter operators
+----------------
+The modules page's filter has three filter operators available. Filter
+operators allow alternative filtering techniques. A filter operator is applied
+by typing within the filter textfield 'operator:' (where operator is the
+operator type) followed immediately with the string to pass to the operator
+function (e.g. 'requires:block'). The available operators are:
+
+description:
+   Filter based on a module's description.
+
+requiredBy:
+   Filter based on what a module is required by.
+
+requires:
+   Filter based on what a module requires.
+
+Multiple filters (or queries) can be applied by space delimiting. For example,
+the filter string 'description:ctools views' would filter down to modules with
+"ctools" in the description and "views" within the module's name. To pass a
+space within a single query wrap it within double quotes (e.g. 'requires:"chaos
+tools"' or '"bulk export"').

+ 23 - 0
sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css

@@ -0,0 +1,23 @@
+html.js #module-filter-submit {
+  background-color: #F6F6F6;
+  width: 239px;
+  border: 1px solid #ccc;
+  border-top: 0;
+}
+html.js #module-filter-submit .form-actions {
+  text-align: center;
+  margin: 0;
+}
+html.js #module-filter-submit input {
+  margin: 2em 0 1em;
+}
+html.js #module-filter-submit.fixed {
+  position: fixed;
+  border-top: 1px solid #ccc;
+}
+html.js #module-filter-submit.fixed-top {
+  top: 0;
+}
+html.js #module-filter-submit.fixed-bottom {
+  bottom: 0;
+}

+ 17 - 1
sites/all/modules/contrib/admin/module_filter/css/module_filter.css

@@ -1,4 +1,20 @@
-
+.module-filter-inputs-wrapper {
+  display: none;
+}
+.module-filter-clear {
+  display: inline;
+  position: relative;
+}
+.module-filter-clear a {
+  margin-left: 5px;
+  font-size: 11px;
+  position: absolute;
+}
 #module-filter-show-wrapper .form-item {
   display: inline;
 }
+.module-filter-no-results {
+  text-align: center;
+  text-transform: uppercase;
+  color: #888;
+}

+ 54 - 0
sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css

@@ -0,0 +1,54 @@
+#module-filter-tabs {
+  float: right;
+}
+#module-filter-tabs li.selected a,
+#module-filter-tabs li.selected a:hover,
+#module-filter-tabs li.selected a:focus,
+#module-filter-tabs li.selected a:active {
+  background-color: #fff;
+  margin-left: -1px;
+  margin-right: 0;
+}
+.admin-operations {
+  float: left;
+}
+#module-filter-modules {
+  margin-right: 240px;
+}
+html.js .toggle-enable {
+  background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, color-stop(50%, red), color-stop(50%, orange), color-stop(100%, orange));
+  background-image: -moz-linear-gradient(right, red 50%, orange 50%, orange 100%);
+  background-image: linear-gradient(right, red 50%, orange 50%, orange 100%);
+}
+html.js .toggle-enable.enabled {
+  background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, color-stop(50%, orange), color-stop(50%, green), color-stop(100%, green));
+  background-image: -moz-linear-gradient(right, orange 50%, green 50%, green 100%);
+  background-image: linear-gradient(right, orange 50%, green 50%, green 100%);
+}
+html.js .toggle-enable.disabled {
+  background: #ccc;
+  border-color: #ddd;
+  cursor: auto;
+}
+html.js .toggle-enable.disabled div {
+  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EEEEEE));
+  background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%);
+  background-image: linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%);
+}
+html.js .toggle-enable div {
+  -webkit-transition: right 0.2s;
+  -mox-transition: right 0.2s;
+  -o-transition: right 0.2s;
+  transition: right 0.2s;
+}
+html.js .toggle-enable div:before {
+  content: "ON";
+  left: -24px;
+}
+html.js .toggle-enable div:after {
+  content: "OFF";
+  left: -24px;
+}
+html.js .toggle-enable.off div {
+  right: 24px;
+}

+ 221 - 95
sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css

@@ -1,136 +1,262 @@
+.sticky-header {
+  z-index: 1;
+}
 
-#module-filter-wrapper .form-item {
-  border: 0px none;
+#module-filter-tabs {
+  float: left;
+}
+#module-filter-tabs ul {
+  width: 239px;
+  list-style: none;
+  list-style-image: none;
+  background-color: #ddd;
+  border: 1px solid #ccc;
+  border-top: none;
   margin: 0;
-  padding: 9px;
+  padding: 0;
+  line-height: 1;
 }
-#module-filter-wrapper .form-item:after {
-/*  display: block;*/
-  clear: none;
+#module-filter-tabs li {
+  background: #eee;
+  border-top: 1px solid #ccc;
+  padding: 0;
+  margin: 0;
+  min-width: 0;
 }
-#module-filter-left {
-  float: left;
-  background-color: #F6F6F6;
-  border: 1px solid #D6DBDE;
-  margin-right: -1px;
-  width: 185px;
+#module-filter-tabs li#new-tab {
+  margin-bottom: 10px;
+  border-bottom: 1px solid #ccc;
 }
-#module-filter-left ul {
-  margin: 0px;
-  padding: 0px;
-  list-style: none;
+#module-filter-tabs li.disabled,
+#module-filter-tabs li#new-tab.disabled {
+  pointer-events: none;
+  cursor: default;
+  background: #ccc;
+  border-top-color: #bbb;
+  border-bottom-color: #bbb;
 }
-#module-filter-left ul li {
-  background: #EFEFEF none repeat scroll 0 0;;
-  border-bottom: 1px solid #D6DBDE;
-  margin: 0px;
-  padding: 0px;
-  list-style-image: none;
+#module-filter-tabs li.disabled a,
+#module-filter-tabs li.disabled span {
+  color: #999;
 }
-#module-filter-left ul li.active {
-  margin-right: -1px;
-  width: 186px;
-  background-color: #FFFFFF;
-  position: relative;
+#module-filter-tabs li.suggest {
+  background: #F9F9F9;
 }
-#module-filter-left ul li a {
-  color: #777777;
+#module-filter-tabs li a {
   display: block;
-  padding: 0.5em;
-  line-height: 100%;
-  font-size: 90%;
-  outline: none;
-}
-#module-filter-left ul li.active a {
-  background-color: #FFFFFF;
-  color: #000000;
-  font-weight: bold;
-}
-#module-filter-left ul li a:hover {
-  background-color: #F6F6F6;
   text-decoration: none;
+  padding: 10px;
 }
-#module-filter-left ul li.active a:hover {
-  background-color: #FFFFFF;
+#module-filter-tabs li a span.result-info {
+  float: right;
+  font-size: 10px;
+  color: #999;
+  margin-top: 3px;
 }
-#module-filter-left ul li a span.visual-aid {
+#module-filter-tabs li a span.visual-aid {
   font-size: 8px;
-  float: right;
+/*  float: right;*/
+}
+#module-filter-tabs li span.visual-aid {
+  font-weight: bold;
 }
-#module-filter-left ul li a span.enabling {
+#module-filter-tabs li a span.enabling {
   color: green;
 }
-#module-filter-left ul li a span.disabling {
+#module-filter-tabs li a span.disabling {
   color: red;
-  margin-left: 5px;
 }
-#module-filter-left ul li a span.counts {
-  font-weight: normal;
+#module-filter-tabs li strong {
+  font-size: 0.923em;
+}
+#module-filter-tabs li a:hover,
+#module-filter-tabs li a:focus {
+  outline: 1px dotted;
+  background: #d5d5d5;
+  text-decoration: none;
+  outline: 0;
+}
+#module-filter-tabs li.selected a,
+#module-filter-tabs li.selected a:hover,
+#module-filter-tabs li.selected a:focus,
+#module-filter-tabs li.selected a:active {
+  background-color: #fff;
+  margin-right: -1px;
+}
+#module-filter-tabs li .summary {
+  display: block;
+  margin-bottom: 0;
+  color: #666;
+  font-size: 0.846em;
+  padding-top: 0.4em;
+}
+#module-filter-tabs li .summary .count {
   display: none;
-  font-size: 0.8em;
-  color: #333333;
-  padding: 2px 0 0;
 }
-#module-filter-left ul li.active a span.counts {
+#module-filter-tabs li.selected .summary .count {
   display: block;
 }
-#module-filter-submit {
-  margin: 0;
+html.js #module-filter-submit input {
+  margin: 2em 2em 1em;
 }
-#module-filter-submit .form-actions {
+html.js .module-filter-inputs-wrapper {
   text-align: center;
 }
-#module-filter-submit input.form-submit {
-  margin: 1em 0 0;
+html.js .module-filter-inputs-wrapper .form-item {
+  margin: 0;
+  padding: 9px;
 }
-#module-filter-submit.fixed {
-  position: fixed;
-  background-color: #F6F6F6;
-  border: 1px solid #D6DBDE;
-  margin-left: -1px;
-  width: 185px;
+html.js .module-filter-inputs-wrapper label {
+  display: inline;
 }
-#module-filter-submit.fixed-top {
-  top: 0;
+html.js .module-filter-inputs-wrapper input[name="module_filter[name]"] {
+  width: 80%;
 }
-#module-filter-submit.fixed-bottom {
-  bottom: 0;
+html.js #module-filter-show-wrapper {
+  margin-bottom: 1em;
 }
-#module-filter-right {
-  display: block;
+html.js #module-filter-modules {
+  margin-left: 240px;
+  border: 1px solid #ccc;
 }
-#module-filter-squeeze {
-  margin-left: 186px;
-  background-color: #FFFFFF;
-  border: 1px solid #D6DBDE;
-  height: auto !important;
+#module-filter-modules table {
+  border-top: none;
+  border-right: none;
+  border-left: none;
 }
-.form-item-module-filter-name {
-  text-align: center;
+html.js #module-filter-modules table {
+  margin: 0;
+  border-bottom: none;
 }
-.form-item-module-filter-name label {
-  display: inline;
+html.js #module-filter-modules table tr,
+html.js #module-filter-modules table td {
+  border-left: 0;
+  border-right: 0;
 }
-#module-filter-show-wrapper .form-checkboxes {
-  text-align: center;
+#module-filter-modules table thead {
+  display: none;
 }
-#module-filter-show-wrapper .form-item:after {
-  display: inline;
+#module-filter-modules table tr.admin-package-title,
+#module-filter-modules table tr.admin-package-title td {
+  border: none !important;
+  border-top: 1px solid #ccc !important;
+  background-color: transparent !important;
+  padding: 10px 0 0;
 }
-table.package {
-  margin: 1em 0;
+#module-filter-modules table tr.admin-package-title.first,
+#module-filter-modules table tr.admin-package-title.first td {
+  border-top: none !important;
 }
-table.package,
-table.package thead,
-table.package tbody,
-table.package tbody tr,
-table.package td:last-child {
-  border-right: 0 none;
-  border-left: 0 none;
+#module-filter-modules table tr.admin-package-header td {
+  border: 1px solid #ccc;
+  text-transform: uppercase;
+  background: #E1E2DC;
+  font-weight: normal;
+  padding: 3px 10px;
 }
-table.package tr.enabling {
+#module-filter-modules table tr.enabling {
   background-color: #dfd;
 }
-table.package tr.disabling {
+#module-filter-modules table tr.disabling {
   background-color: #fcc;
 }
+#module-filter-modules span.module-machine-name {
+  font-size: 0.9em;
+  font-weight: normal;
+}
+.admin-version {
+  white-space: nowrap;
+}
+.admin-operations {
+  float: right;
+}
+.admin-operations a.module-link {
+  display: inline;
+}
+
+html.js .toggle-enable {
+  margin: auto;
+  position: relative;
+  width: 50px;
+  overflow: hidden;
+  height: 18px;
+  line-height: 18px;
+  font-size: 11px;
+  text-align: center;
+  cursor: pointer;
+  border: 1px solid #ccc;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  -khtml-border-radius: 3px;
+  border-radius: 3px;
+  -moz-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
+  -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
+  box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
+  background-clip: padding-box;
+  background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, red), color-stop(50%, orange), color-stop(100%, orange));
+  background-image: -moz-linear-gradient(left, red 50%, orange 50%, orange 100%);
+  background-image: linear-gradient(left, red 50%, orange 50%, orange 100%);
+}
+html.js .toggle-enable.enabled {
+  background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, orange), color-stop(50%, green), color-stop(100%, green));
+  background-image: -moz-linear-gradient(left, orange 50%, green 50%, green 100%);
+  background-image: linear-gradient(left, orange 50%, green 50%, green 100%);
+}
+html.js .toggle-enable.disabled {
+  background: #ccc;
+  border-color: #ddd;
+  cursor: auto;
+}
+html.js .toggle-enable div {
+  position: relative;
+  color: #777;
+  width: 26px;
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  -khtml-border-radius: 2px;
+  border-radius: 2px;
+  background: white;
+  text-shadow: 1px 1px 0 white;
+  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EAEAEA));
+  background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%);
+  background-image: linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%);
+  -webkit-transition: left 0.2s;
+  -mox-transition: left 0.2s;
+  -o-transition: left 0.2s;
+  transition: left 0.2s;
+}
+html.js .toggle-enable.disabled div {
+  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EEEEEE));
+  background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%);
+  background-image: linear-gradient(top, #FEFEFE 0%, #EEEEEE 100%);
+}
+html.js .toggle-enable div:after,
+html.js .toggle-enable div:before {
+  color: white;
+  text-shadow: none;
+  width: 25px;
+  position: absolute;
+  top: 0;
+  font-size: 9px;
+  font-weight: bold;
+}
+html.js .toggle-enable div:before {
+  content: "OFF";
+  left: -24px;
+}
+html.js .toggle-enable div:after {
+  content: "ON";
+  right: -24px;
+}
+html.js .toggle-enable.off div {
+  left: 24px;
+}
+
+#module-filter-tabs.top-fixed {
+  position: fixed;
+  top: 0;
+}
+#module-filter-tabs.bottom-fixed {
+  position: fixed;
+  bottom: 0;
+}

+ 47 - 0
sites/all/modules/contrib/admin/module_filter/css/modules.css

@@ -0,0 +1,47 @@
+#system-modules table {
+  table-layout: fixed;
+}
+#system-modules th.checkbox {
+  width: 8%;
+}
+#system-modules th.name {
+  width: 25%;
+}
+#system-modules th.links {
+  width: 15%;
+}
+#system-modules td {
+  vertical-align: top;
+}
+#system-modules .expand.inner {
+  background: transparent url(/misc/menu-collapsed.png) left 0.6em no-repeat;
+  margin-left: -12px;
+  padding-left: 12px;
+}
+#system-modules .expanded.expand.inner {
+  background: transparent url(/misc/menu-expanded.png) left 0.6em no-repeat;
+}
+#system-modules .description {
+  cursor: pointer;
+}
+#system-modules .description .inner {
+  overflow: hidden; /* truncates descriptions if too long */
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+#system-modules .description .requirements,
+#system-modules .description .links {
+  display: none;
+}
+#system-modules .description .expanded.inner {
+  overflow: visible;
+  white-space: normal;
+}
+#system-modules .description .expanded .requirements,
+#system-modules .description .expanded .links {
+  display: block;
+}
+#system-modules .requirements {
+  padding: 5px 0;
+  max-width: 490px;
+}

+ 18 - 0
sites/all/modules/contrib/admin/module_filter/css/update_status.css

@@ -0,0 +1,18 @@
+#module-filter-update-status-form {
+  float: right;
+}
+.module-filter-inputs-wrapper label {
+  display: inline;
+}
+.module-filter-inputs-wrapper .form-item-module-filter-name {
+  margin-bottom: 0;
+  padding-bottom: 0;
+  text-align: right;
+}
+#module-filter-show-wrapper .form-item {
+  padding: 5px;
+}
+p.module-filter-no-results {
+  clear: both;
+  padding-top: 30px;
+}

+ 36 - 21
sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js

@@ -1,33 +1,48 @@
 (function($) {
-  Drupal.behaviors.moduleFilterDynamicPosition = {
-    attach: function() {
-      $(window).scroll(function() {
+
+Drupal.behaviors.moduleFilterDynamicPosition = {
+  attach: function(context) {
+    var $window = $(window);
+
+    $('#module-filter-wrapper', context).once('dynamic-position', function() {
+      // Move the submit button just below the tabs.
+      $('#module-filter-tabs').append($('#module-filter-submit'));
+
+      var positionSubmit = function() {
+        var $tabs = $('#module-filter-tabs');
+        var $submit = $('#module-filter-submit', $tabs);
+
         // Vertical movement.
-        var top = $('#module-filter-tabs').offset().top;
-        var bottom = top + $('#module-filter-tabs').height();
-        var windowHeight = $(window).height();
-        if (((bottom - windowHeight) > ($(window).scrollTop() - $('#module-filter-submit').height())) && $(window).scrollTop() + windowHeight - $('#module-filter-submit').height() - $('#all-tab').height() > top) {
-          $('#module-filter-submit').removeClass('fixed-top').addClass('fixed fixed-bottom');
+        var bottom = $tabs.offset().top + $tabs.outerHeight();
+        if ($submit.hasClass('fixed-bottom')) {
+          bottom += $submit.height();
         }
-        else if (bottom < $(window).scrollTop()) {
-          $('#module-filter-submit').removeClass('fixed-bottom').addClass('fixed fixed-top');
+        if (bottom >= $window.height() + $window.scrollTop()) {
+          $submit.addClass('fixed fixed-bottom');
+          $tabs.css('padding-bottom', $submit.height());
         }
         else {
-          $('#module-filter-submit').removeClass('fixed fixed-bottom fixed-top');
+          $submit.removeClass('fixed fixed-bottom');
+          $tabs.css('padding-bottom', 0);
         }
 
         // Horizontal movement.
-        if ($('#module-filter-submit').hasClass('fixed-bottom') || $('#module-filter-submit').hasClass('fixed-top')) {
-          var left = $('#module-filter-tabs').offset().left - $(window).scrollLeft();
-          if (left != $('#module-filter-submit').offset().left - $(window).scrollLeft()) {
-            $('#module-filter-submit').css('left', left);
+        if ($submit.hasClass('fixed-bottom') || $submit.hasClass('fixed-top')) {
+          var left = $tabs.offset().left - $window.scrollLeft();
+          if (left != $submit.offset().left - $window.scrollLeft()) {
+            $submit.css('left', left);
           }
         }
-      });
-      $(window).trigger('scroll');
-      $(window).resize(function() {
-        $(window).trigger('scroll');
-      });
-    }
+      };
+
+      // Control the positioning.
+      $window.scroll(positionSubmit);
+      $window.resize(positionSubmit);
+      var moduleFilter = $('input[name="module_filter[name]"]').data('moduleFilter');
+      moduleFilter.element.bind('moduleFilter:adjustHeight', positionSubmit);
+      moduleFilter.adjustHeight();
+    });
   }
+};
+
 })(jQuery);

+ 245 - 94
sites/all/modules/contrib/admin/module_filter/js/module_filter.js

@@ -1,120 +1,271 @@
-
 (function ($) {
-  var moduleFilterTimeOut;
-  var moduleFilterTextFilter = '';
-
-  Drupal.behaviors.moduleFilter = {
-    attach: function() {
-      $("#module-filter-wrapper").show();
-      $('input[name="module_filter[name]"]').focus();
-      $('input[name="module_filter[name]"]').keyup(function(e) {
-        switch (e.which) {
-          case 13:
-            if (moduleFilterTimeOut) {
-              clearTimeout(moduleFilterTimeOut);
-            }
-
-            moduleFilter(moduleFilterTextFilter);
-            break;
-          default:
-            if (moduleFilterTextFilter != $(this).val()) {
-              moduleFilterTextFilter = this.value;
-              if (moduleFilterTimeOut) {
-                clearTimeout(moduleFilterTimeOut);
-              }
-
-              moduleFilterTimeOut = setTimeout('moduleFilter("' + moduleFilterTextFilter + '")', 500);
-            }
-            break;
+
+Drupal.ModuleFilter = {};
+
+Drupal.ModuleFilter.explode = function(string) {
+  var queryArray = string.match(/([a-zA-Z]+\:(\w+|"[^"]+")*)|\w+|"[^"]+"/g);
+  if (!queryArray) {
+    queryArray = new Array();
+  }
+  var i = queryArray.length;
+  while (i--) {
+    queryArray[i] = queryArray[i].replace(/"/g, "");
+  }
+  return queryArray;
+};
+
+Drupal.ModuleFilter.getState = function(key) {
+  if (!Drupal.ModuleFilter.state) {
+    Drupal.ModuleFilter.state = {};
+    var cookie = $.cookie('DrupalModuleFilter');
+    var query = cookie ? cookie.split('&') : [];
+    if (query) {
+      for (var i in query) {
+        // Extra check to avoid js errors in Chrome, IE and Safari when
+        // combined with JS like twitter's widget.js.
+        // See http://drupal.org/node/798764.
+        if (typeof(query[i]) == 'string' && query[i].indexOf('=') != -1) {
+          var values = query[i].split('=');
+          if (values.length === 2) {
+            Drupal.ModuleFilter.state[values[0]] = values[1];
+          }
         }
-      });
-      $('input[name="module_filter[name]"]').keypress(function(e) {
-        if (e.which == 13) e.preventDefault();
-      });
-
-      $('#edit-module-filter-show-enabled').change(function() {
-        moduleFilter($('input[name="module_filter[name]"]').val());
-      });
-      $('#edit-module-filter-show-disabled').change(function() {
-        moduleFilter($('input[name="module_filter[name]"]').val());
-      });
-      $('#edit-module-filter-show-required').change(function() {
-        moduleFilter($('input[name="module_filter[name]"]').val());
-      });
-      $('#edit-module-filter-show-unavailable').change(function() {
-        moduleFilter($('input[name="module_filter[name]"]').val());
-      });
+      }
+    }
+  }
+  return Drupal.ModuleFilter.state[key] ? Drupal.ModuleFilter.state[key] : false;
+};
+
+Drupal.ModuleFilter.setState = function(key, value) {
+  var existing = Drupal.ModuleFilter.getState(key);
+  if (existing != value) {
+    Drupal.ModuleFilter.state[key] = value;
+    var query = [];
+    for (var i in Drupal.ModuleFilter.state) {
+      query.push(i + '=' + Drupal.ModuleFilter.state[i]);
     }
+    $.cookie('DrupalModuleFilter', query.join('&'), { expires: 7, path: '/' });
   }
+};
+
+Drupal.ModuleFilter.Filter = function(element, selector, options) {
+  var self = this;
+
+  this.element = element;
+  this.text = $(this.element).val();
+
+  this.settings = Drupal.settings.moduleFilter;
 
-  moduleFilter = function(string) {
-    stringLowerCase = string.toLowerCase();
+  this.selector = selector;
 
-    $("fieldset table tbody tr td label strong").each(function(i) {
-      var $row = $(this).parents('tr');
-      var module = $(this).text();
-      var moduleLowerCase = module.toLowerCase();
-      var $fieldset = $row.parents('fieldset');
+  this.options = $.extend({
+    delay: 500,
+    striping: false,
+    childSelector: null,
+    empty: Drupal.t('No results'),
+    rules: new Array()
+  }, options);
+  if (this.options.wrapper == undefined) {
+    this.options.wrapper = $(self.selector).parent();
+  }
+
+  // Add clear button.
+  this.element.after('<div class="module-filter-clear"><a href="#" class="js-hide">' + Drupal.t('clear') + '</a></div>');
+  if (this.text) {
+    $('.module-filter-clear a', this.element.parent()).removeClass('js-hide');
+  }
+  $('.module-filter-clear a', this.element.parent()).click(function() {
+    self.element.val('');
+    self.text = '';
+    delete self.queries;
+    self.applyFilter();
+    self.element.focus();
+    $(this).addClass('js-hide');
+    return false;
+  });
 
-      if (string != '') {
-        if ($fieldset.hasClass('collapsed')) {
-          $fieldset.removeClass('collapsed');
+  this.updateQueries = function() {
+    var queryStrings = Drupal.ModuleFilter.explode(self.text);
+
+    self.queries = new Array();
+    for (var i in queryStrings) {
+      var query = { operator: 'text', string: queryStrings[i] };
+
+      if (self.operators != undefined) {
+        // Check if an operator is possibly used.
+        if (queryStrings[i].indexOf(':') > 0) {
+          // Determine operator used.
+          var args = queryStrings[i].split(':', 2);
+          var operator = args.shift();
+          if (self.operators[operator] != undefined) {
+            query.operator = operator;
+            query.string = args.shift();
+          }
         }
       }
 
-      if (moduleLowerCase.match(stringLowerCase)) {
-        if (moduleFilterVisible($('td.checkbox input', $row))) {
-          if (!$row.is(':visible')) {
-            $row.show();
-            if ($fieldset.hasClass('collapsed')) {
-              $fieldset.removeClass('collapsed');
-            }
-            if (!$fieldset.is(':visible')) {
-              $fieldset.show();
-            }
+      query.string = query.string.toLowerCase();
+
+      self.queries.push(query);
+    }
+
+    if (self.queries.length <= 0) {
+      // Add a blank string query.
+      self.queries.push({ operator: 'text', string: '' });
+    }
+  };
+
+  this.applyFilter = function() {
+    self.results = new Array();
+
+    self.updateQueries();
+
+    if (self.index == undefined) {
+      self.buildIndex();
+    }
+
+    self.element.trigger('moduleFilter:start');
+
+    $.each(self.index, function(key, item) {
+      var $item = item.element;
+
+      for (var i in self.queries) {
+        var query = self.queries[i];
+        if (query.operator == 'text') {
+          if (item.text.indexOf(query.string) < 0) {
+            continue;
           }
         }
         else {
-          $row.hide();
-          if ($row.siblings(':visible').html() == null) {
-            $fieldset.hide();
-          }
-        }
-      }
-      else {
-        if ($row.is(':visible')) {
-          $row.hide();
-          if ($row.siblings(':visible').html() == null) {
-            $fieldset.hide();
+          var func = self.operators[query.operator];
+          if (!(func(query.string, self, item))) {
+            continue;
           }
         }
-      }
-    });
-  }
 
-  moduleFilterVisible = function(checkbox) {
-    if (checkbox.length > 0) {
-      if ($('#edit-module-filter-show-enabled').is(':checked')) {
-        if ($(checkbox).is(':checked') && !$(checkbox).is(':disabled')) {
+        var rulesResult = self.processRules(item);
+        if (rulesResult !== false) {
           return true;
         }
       }
-      if ($('#edit-module-filter-show-disabled').is(':checked')) {
-        if (!$(checkbox).is(':checked') && !$(checkbox).is(':disabled')) {
-          return true;
+
+      $item.addClass('js-hide');
+    });
+
+    self.element.trigger('moduleFilter:finish', { results: self.results });
+
+    if (self.options.striping) {
+      self.stripe();
+    }
+
+    if (self.results.length > 0) {
+      self.options.wrapper.find('.module-filter-no-results').remove();
+    }
+    else {
+      if (!self.options.wrapper.find('.module-filter-no-results').length) {
+        self.options.wrapper.append($('<p class="module-filter-no-results"/>').text(self.options.empty));
+      };
+    }
+  };
+
+  self.element.keyup(function(e) {
+    switch (e.which) {
+      case 13:
+        if (self.timeOut) {
+          clearTimeout(self.timeOut);
         }
-      }
-      if ($('#edit-module-filter-show-required').is(':checked')) {
-        if ($(checkbox).is(':checked') && $(checkbox).is(':disabled')) {
-          return true;
+        self.applyFilter();
+        break;
+      default:
+        if (self.text != $(this).val()) {
+          if (self.timeOut) {
+            clearTimeout(self.timeOut);
+          }
+
+          self.text = $(this).val();
+
+          if (self.text) {
+            self.element.parent().find('.module-filter-clear a').removeClass('js-hide');
+          }
+          else {
+            self.element.parent().find('.module-filter-clear a').addClass('js-hide');
+          }
+
+          self.element.trigger('moduleFilter:keyup');
+
+          self.timeOut = setTimeout(self.applyFilter, self.options.delay);
         }
-      }
+        break;
     }
-    if ($('#edit-module-filter-show-unavailable').is(':checked')) {
-      if (checkbox.length == 0 || ($(checkbox).size() > 0 && !$(checkbox).is(':checked') && $(checkbox).is(':disabled'))) {
-        return true;
+  });
+
+  self.element.keypress(function(e) {
+    if (e.which == 13) e.preventDefault();
+  });
+};
+
+Drupal.ModuleFilter.Filter.prototype.buildIndex = function() {
+  var self = this;
+  var index = new Array();
+  $(this.selector).each(function(i) {
+    var text = (self.options.childSelector) ? $(self.options.childSelector, this).text() : $(this).text();
+    var item = {
+      key: i,
+      element: $(this),
+      text: text.toLowerCase()
+    };
+    for (var j in self.options.buildIndex) {
+      var func = self.options.buildIndex[j];
+      item = $.extend(func(self, item), item);
+    }
+    $(this).data('indexKey', i);
+    index.push(item);
+    delete item;
+  });
+  this.index = index;
+};
+
+Drupal.ModuleFilter.Filter.prototype.processRules = function(item) {
+  var self = this;
+  var $item = item.element;
+  var rulesResult = true;
+  if (self.options.rules.length > 0) {
+    for (var i in self.options.rules) {
+      var func = self.options.rules[i];
+      rulesResult = func(self, item);
+      if (rulesResult === false) {
+        break;
       }
     }
-    return false;
   }
+  if (rulesResult !== false) {
+    $item.removeClass('js-hide');
+    self.results.push(item);
+  }
+  return rulesResult;
+};
+
+Drupal.ModuleFilter.Filter.prototype.stripe = function() {
+  var self = this;
+  var flip = { even: 'odd', odd: 'even' };
+  var stripe = 'odd';
+
+  $.each(self.index, function(key, item) {
+    if (!item.element.hasClass('js-hide')) {
+      item.element.removeClass('odd even')
+        .addClass(stripe);
+      stripe = flip[stripe];
+    }
+  });
+};
+
+$.fn.moduleFilter = function(selector, options) {
+  var filterInput = this;
+  filterInput.parents('.module-filter-inputs-wrapper').show();
+  if (Drupal.settings.moduleFilter.setFocus) {
+    filterInput.focus();
+  }
+  filterInput.data('moduleFilter', new Drupal.ModuleFilter.Filter(this, selector, options));
+};
+
 })(jQuery);

+ 508 - 227
sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js

@@ -1,281 +1,562 @@
 
 (function ($) {
-  Drupal.ModuleFilter = Drupal.ModuleFilter || {};
-  Drupal.ModuleFilter.textFilter = '';
-  Drupal.ModuleFilter.timeout;
-  Drupal.ModuleFilter.tabs = {};
-  Drupal.ModuleFilter.enabling = {};
-  Drupal.ModuleFilter.disabling = {};
-
-  Drupal.behaviors.moduleFilter = {
-    attach: function() {
-      // Set the focus on the module filter textfield.
-      $('input[name="module_filter[name]"]').focus();
-
-      $('#module-filter-squeeze').css('min-height', $('#module-filter-tabs').height());
-
-      $('#module-filter-left a.project-tab').each(function(i) {
-        Drupal.ModuleFilter.tabs[$(this).attr('id')] = new Drupal.ModuleFilter.Tab(this);
-      });
 
-      // Move anchors to top of tabs.
-      $('a.anchor', $('#module-filter-left')).remove().prependTo('#module-filter-tabs');
+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) {
+      $('#module-filter-wrapper table:not(.sticky-header)', context).once('module-filter-tabs', function() {
+        var $modules = $('#module-filter-modules');
+        var moduleFilter = $('input[name="module_filter[name]"]').data('moduleFilter');
+        var table = $(this);
+
+        $('thead', table).show();
+
+        // Remove package header rows.
+        $('tr.admin-package-header', table).remove();
 
-      $('input[name="module_filter[name]"]').keyup(function(e) {
-        switch (e.which) {
-          case 13:
-            if (Drupal.ModuleFilter.timeout) {
-              clearTimeout(Drupal.ModuleFilter.timeout);
+        var $tabsWrapper = $('<div id="module-filter-tabs"></div>');
+
+        // Build tabs from package title rows.
+        var tabs = '<ul>';
+        for (var i in Drupal.settings.moduleFilter.packageIDs) {
+          var id = Drupal.settings.moduleFilter.packageIDs[i];
+
+          var name = id;
+          var tabClass = 'project-tab';
+          var title = null;
+          var summary = (Drupal.settings.moduleFilter.countEnabled) ? '<span class="count">' + Drupal.ModuleFilter.countSummary(id) + '</span>' : '';
+
+          switch (id) {
+            case 'all':
+              name = Drupal.t('All');
+              break;
+            case 'new':
+              name = Drupal.t('New');
+              title = Drupal.t('Modules installed within the last week.');
+              if (Drupal.settings.moduleFilter.enabledCounts['new'].total == 0) {
+                tabClass += ' disabled';
+                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());
+              $row.remove();
+              break;
+          }
+
+          tabs += '<li id="' + id + '-tab" class="' + tabClass + '"><a href="#' + id + '" class="overlay-exclude"' + (title ? ' title="' + title + '"' : '') + '><strong>' + name + '</strong><span class="summary">' + summary + '</span></a></li>';
+        }
+        tabs += '</ul>';
+        $tabsWrapper.append(tabs);
+        $modules.before($tabsWrapper);
+
+        // Index tabs.
+        $('#module-filter-tabs li').each(function() {
+          var $tab = $(this);
+          var id = $tab.attr('id');
+          Drupal.ModuleFilter.tabs[id] = new Drupal.ModuleFilter.Tab($tab, id);
+        });
+
+        $('tbody td.checkbox input', $modules).change(function() {
+          var $checkbox = $(this);
+          var key = $checkbox.parents('tr').data('indexKey');
+
+          moduleFilter.index[key].status = $checkbox.is(':checked');
+
+          if (Drupal.settings.moduleFilter.visualAid) {
+            var type = ($checkbox.is(':checked')) ? 'enable' : 'disable';
+            Drupal.ModuleFilter.updateVisualAid(type, $checkbox.parents('tr'));
+          }
+        });
+
+        // Sort rows.
+        var rows = $('tbody tr.module', table).get();
+        rows.sort(function(a, b) {
+          var compA = $('td:nth(1)', a).text().toLowerCase();
+          var compB = $('td:nth(1)', b).text().toLowerCase();
+          return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
+        });
+        $.each(rows, function(idx, itm) { table.append(itm); });
+
+        // Re-stripe rows.
+        $('tr.module', table)
+          .removeClass('odd even')
+          .filter(':odd').addClass('even').end()
+          .filter(':even').addClass('odd');
+
+        moduleFilter.adjustHeight();
+
+        moduleFilter.element.bind('moduleFilter:start', function() {
+          moduleFilter.tabResults = {
+            'all-tab': { items: {}, count: 0 },
+            'recent-tab': { items: {}, count: 0 },
+            'new-tab': { items: {}, count: 0 }
+          };
+
+          // Empty result info from tabs.
+          for (var i in Drupal.ModuleFilter.tabs) {
+            if (Drupal.ModuleFilter.tabs[i].resultInfo != undefined) {
+              Drupal.ModuleFilter.tabs[i].resultInfo.empty();
             }
+          }
+        });
+
+        moduleFilter.element.bind('moduleFilter:finish', function(e, data) {
+          $.each(moduleFilter.index, function(key, item) {
+            if (!item.element.hasClass('js-hide')) {
+              var id = Drupal.ModuleFilter.getTabID(item.element);
 
-            Drupal.ModuleFilter.filter(Drupal.ModuleFilter.textFilter);
-            break;
-          default:
-            if (Drupal.ModuleFilter.textFilter != $(this).val()) {
-              Drupal.ModuleFilter.textFilter = this.value;
-              if (Drupal.ModuleFilter.timeout) {
-                clearTimeout(Drupal.ModuleFilter.timeout);
+              if (moduleFilter.tabResults[id] == undefined) {
+                moduleFilter.tabResults[id] = { items: {}, count: 0 };
+              }
+              if (moduleFilter.tabResults[id].items[item.key] == undefined) {
+                // 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++;
+                }
+
+                moduleFilter.tabResults[id].items[item.key] = item;
+                moduleFilter.tabResults[id].count++;
+              }
+
+              if (Drupal.ModuleFilter.activeTab != undefined && Drupal.ModuleFilter.activeTab.id != 'all-tab') {
+                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');
+                }
               }
-              Drupal.ModuleFilter.timeout = setTimeout('Drupal.ModuleFilter.filter("' + Drupal.ModuleFilter.textFilter + '")', 500);
             }
-            break;
-        }
-      });
-      $('input[name="module_filter[name]"]').keypress(function(e) {
-        if (e.which == 13) e.preventDefault();
-      });
+          });
 
-      Drupal.ModuleFilter.showEnabled = $('#edit-module-filter-show-enabled').is(':checked');
-      $('#edit-module-filter-show-enabled').change(function() {
-        Drupal.ModuleFilter.showEnabled = $(this).is(':checked');
-        Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val());
-      });
-      Drupal.ModuleFilter.showDisabled = $('#edit-module-filter-show-disabled').is(':checked');
-      $('#edit-module-filter-show-disabled').change(function() {
-        Drupal.ModuleFilter.showDisabled = $(this).is(':checked');
-        Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val());
-      });
-      Drupal.ModuleFilter.showRequired = $('#edit-module-filter-show-required').is(':checked');
-      $('#edit-module-filter-show-required').change(function() {
-        Drupal.ModuleFilter.showRequired = $(this).is(':checked');
-        Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val());
-      });
-      Drupal.ModuleFilter.showUnavailable = $('#edit-module-filter-show-unavailable').is(':checked');
-      $('#edit-module-filter-show-unavailable').change(function() {
-        Drupal.ModuleFilter.showUnavailable = $(this).is(':checked');
-        Drupal.ModuleFilter.filter($('input[name="module_filter[name]"]').val());
-      });
+          if (Drupal.settings.moduleFilter.visualAid) {
+            if (moduleFilter.text) {
+              // Add result info to tabs.
+              for (var id in moduleFilter.tabResults) {
+                var tab = Drupal.ModuleFilter.tabs[id];
 
-      if (Drupal.settings.moduleFilter.visualAid == 1) {
-        $('table.package tbody td.checkbox input').change(function() {
-          if ($(this).is(':checked')) {
-            Drupal.ModuleFilter.updateVisualAid('enable', $(this).parents('tr'));
+                if (tab.resultInfo == undefined) {
+                  var resultInfo = '<span class="result-info"></span>'
+                  $('a', tab.element).prepend(resultInfo);
+                  tab.resultInfo = $('span.result-info', tab.element);
+                }
+
+                tab.resultInfo.append(moduleFilter.tabResults[id].count);
+              }
+
+              if (Drupal.settings.moduleFilter.hideEmptyTabs) {
+                for (var id in Drupal.ModuleFilter.tabs) {
+                  if (moduleFilter.tabResults[id] != undefined) {
+                    Drupal.ModuleFilter.tabs[id].element.show();
+                  }
+                  else if (Drupal.ModuleFilter.activeTab == undefined || Drupal.ModuleFilter.activeTab.id != id) {
+                    Drupal.ModuleFilter.tabs[id].element.hide();
+                  }
+                }
+              }
+            }
+            else {
+              // Make sure all tabs are visible.
+              if (Drupal.settings.moduleFilter.hideEmptyTabs) {
+                $('#module-filter-tabs li').show();
+              }
+            }
           }
-          else {
-            Drupal.ModuleFilter.updateVisualAid('disable', $(this).parents('tr'));
+
+          if ((Drupal.ModuleFilter.activeTab != undefined && (moduleFilter.tabResults[Drupal.ModuleFilter.activeTab.id] == undefined || moduleFilter.tabResults[Drupal.ModuleFilter.activeTab.id].count <= 0))) {
+            // The current tab contains no results.
+            moduleFilter.results = 0;
           }
+
+          moduleFilter.adjustHeight();
         });
-      }
 
-      // Check for anchor.
-      var url = document.location.toString();
-      if (url.match('#')) {
-        // Make tab active based on anchor.
-        var anchor = '#' + url.split('#')[1];
-        $('a[href="' + anchor + '"]').click();
-      }
-      // Else if no active tab is defined, set it to the all tab.
-      else if (Drupal.ModuleFilter.activeTab == undefined) {
-        Drupal.ModuleFilter.activeTab = Drupal.ModuleFilter.tabs['all-tab'];
-      }
-    }
-  }
+        if (Drupal.settings.moduleFilter.useURLFragment) {
+          $(window).bind('hashchange.module-filter', $.proxy(Drupal.ModuleFilter, 'eventHandlerOperateByURLFragment')).triggerHandler('hashchange.module-filter');
+        }
+        else {
+          Drupal.ModuleFilter.selectTab();
+        }
 
-  Drupal.ModuleFilter.visible = function(checkbox) {
-    if (checkbox.length > 0) {
-      if (Drupal.ModuleFilter.showEnabled) {
-        if ($(checkbox).is(':checked') && !$(checkbox).is(':disabled')) {
-          return true;
+        if (Drupal.settings.moduleFilter.useSwitch) {
+          $('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')) {
+                if (Drupal.ModuleFilter.jQueryIsNewer()) {
+                  $checkbox.click();
+                }
+                else {
+                  $checkbox.click().change();
+                }
+              }
+            });
+            $checkbox.click(function() {
+              if (!$switch.hasClass('disabled')) {
+                $switch.toggleClass('off');
+              }
+            });
+          });
         }
-      }
-      if (Drupal.ModuleFilter.showDisabled) {
-        if (!$(checkbox).is(':checked') && !$(checkbox).is(':disabled')) {
-          return true;
+
+        var $tabs = $('#module-filter-tabs');
+
+        function getParentTopOffset($obj, offset) {
+          var $parent = $obj.offsetParent();
+          if ($obj[0] != $parent[0]) {
+            offset += $parent.position().top;
+            return getParentTopOffset($parent, offset);
+          }
+          return offset;
         }
-      }
-      if (Drupal.ModuleFilter.showRequired) {
-        if ($(checkbox).is(':checked') && $(checkbox).is(':disabled')) {
-          return true;
+
+        var tabsTopOffset = null;
+        function getParentsTopOffset() {
+          if (tabsTopOffset === null) {
+            tabsTopOffset = getParentTopOffset($tabs.parent(), 0);
+          }
+          return tabsTopOffset;
+        }
+
+        function viewportTop() {
+          var top = $(window).scrollTop();
+          return top;
         }
-      }
-    }
-    if (Drupal.ModuleFilter.showUnavailable) {
-      if (checkbox.length == 0 || (!$(checkbox).is(':checked') && $(checkbox).is(':disabled'))) {
-        return true;
-      }
-    }
-    return false;
-  }
 
-  Drupal.ModuleFilter.filter = function(string) {
-    var stringLowerCase = string.toLowerCase();
-    var flip = 'odd';
+        function viewportBottom() {
+          var top = $(window).scrollTop();
+          var bottom = top + $(window).height();
 
-    if (Drupal.ModuleFilter.activeTab.id == 'all-tab') {
-      var selector = 'table.package tbody tr td label > strong';
-    }
-    else {
-      var selector = 'table.package tbody tr.' + Drupal.ModuleFilter.activeTab.id + '-content td label strong';
-    }
+          bottom -= $('#page-actions').height();
 
-    $(selector).each(function(i) {
-      var $row = $(this).parents('tr');
-      var module = $(this).text();
-      var moduleLowerCase = module.toLowerCase();
-
-      if (moduleLowerCase.match(stringLowerCase)) {
-        if (Drupal.ModuleFilter.visible($('td.checkbox :checkbox', $row))) {
-          $row.removeClass('odd even');
-          $row.addClass(flip);
-          $row.show();
-          flip = (flip == 'odd') ? 'even' : 'odd';
+          return bottom;
         }
-        else {
-          $row.hide();
+
+        function fixToTop(top) {
+          if ($tabs.hasClass('bottom-fixed')) {
+            $tabs.css({
+              'position': 'absolute',
+              'top': $tabs.position().top - getParentsTopOffset(),
+              'bottom': 'auto'
+            });
+            $tabs.removeClass('bottom-fixed');
+          }
+
+          if (($tabs.css('position') == 'absolute' && $tabs.offset().top - top >= 0) || ($tabs.css('position') != 'absolute' && $tabs.offset().top - top <= 0)) {
+            $tabs.addClass('top-fixed');
+            $tabs.attr('style', '');
+          }
+        }
+
+        function fixToBottom(bottom) {
+          if ($tabs.hasClass('top-fixed')) {
+            $tabs.css({
+              'position': 'absolute',
+              'top': $tabs.position().top - getParentsTopOffset(),
+              'bottom': 'auto'
+            });
+            $tabs.removeClass('top-fixed');
+          }
+
+          if ($tabs.offset().top + $tabs.height() - bottom <= 0) {
+            $tabs.addClass('bottom-fixed');
+            var style = '';
+            var pageActionsHeight = $('#page-actions').height();
+            if (pageActionsHeight > 0) {
+              style = 'bottom: ' + pageActionsHeight + 'px';
+            }
+            else if (Drupal.settings.moduleFilter.dynamicPosition) {
+              // style = 'bottom: ' + $('#module-filter-submit', $tabs).height() + 'px';
+            }
+            $tabs.attr('style', style);
+          }
         }
-      }
-      else {
-        $row.hide();
-      }
-    });
-  }
 
-  Drupal.ModuleFilter.Tab = function(element) {
-    this.id = $(element).attr('id');
-    this.element = element;
+        var lastTop = 0;
+        $(window).scroll(function() {
+          var top = viewportTop();
+          var bottom = viewportBottom();
 
-    $(this.element).click(function() {
-      Drupal.ModuleFilter.tabs[$(this).attr('id')].setActive();
-    });
+          if ($modules.offset().top >= top) {
+            $tabs.removeClass('top-fixed').attr('style', '');
+          }
+          else {
+            if (top > lastTop) { // Downward scroll.
+              if ($tabs.height() > bottom - top) {
+                fixToBottom(bottom);
+              }
+              else {
+                fixToTop(top);
+              }
+            }
+            else { // Upward scroll.
+              fixToTop(top);
+            }
+          }
+          lastTop = top;
+        });
+
+        moduleFilter.adjustHeight();
+      });
+    }
   }
+};
+
+Drupal.ModuleFilter.Tab = function(element, id) {
+  var self = this;
+
+  this.id = id;
+  this.hash = id.substring(0, id.length - 4);
+  this.element = element;
+
+  $('a', this.element).click(function() {
+    if (!Drupal.settings.moduleFilter.useURLFragment) {
+      var hash = (!self.element.hasClass('selected')) ? self.hash : 'all';
+      Drupal.ModuleFilter.selectTab(hash);
+      return false;
+    }
 
-  Drupal.ModuleFilter.Tab.prototype.setActive = function() {
-    if (Drupal.ModuleFilter.activeTab) {
-      $(Drupal.ModuleFilter.activeTab.element).parent().removeClass('active');
+    if (self.element.hasClass('selected')) {
+      // Clear the active tab.
+      window.location.hash = 'all';
+      return false;
     }
-    // Assume the default active tab is #all-tab. Remove its active class.
-    else {
-      $('#all-tab').parent().removeClass('active');
+  });
+
+  $('tr.' + this.id, $('#system-modules')).hover(
+    function() {
+      self.element.addClass('suggest');
+    },
+    function() {
+      self.element.removeClass('suggest');
     }
+  );
+};
 
-    Drupal.ModuleFilter.activeTab = this;
-    $(Drupal.ModuleFilter.activeTab.element).parent().addClass('active');
-    Drupal.ModuleFilter.activeTab.displayRows();
+Drupal.ModuleFilter.selectTab = function(hash) {
+  if (!hash || Drupal.ModuleFilter.tabs[hash + '-tab'] == undefined || Drupal.settings.moduleFilter.enabledCounts[hash].total == 0) {
+    if (Drupal.settings.moduleFilter.rememberActiveTab) {
+      var activeTab = Drupal.ModuleFilter.getState('activeTab');
+      if (activeTab && Drupal.ModuleFilter.tabs[activeTab + '-tab'] != undefined) {
+        hash = activeTab;
+      }
+    }
 
-    // Clear filter textfield and refocus on it.
-    $('input[name="module_filter[name]"]').val('');
-    $('input[name="module_filter[name]"]').focus();
+    if (!hash) {
+      hash = 'all';
+    }
   }
 
-  Drupal.ModuleFilter.Tab.prototype.displayRows = function() {
-    var flip = 'odd';
-    var selector = (Drupal.ModuleFilter.activeTab.id == 'all-tab') ? 'table.package tbody tr' : 'table.package tbody tr.' + this.id + '-content';
-    $('table.package tbody tr').hide();
-    $('table.package tbody tr').removeClass('odd even');
-    $(selector).each(function(i) {
-      if (Drupal.ModuleFilter.visible($('td.checkbox input', $(this)))) {
-        $(this).addClass(flip);
-        flip = (flip == 'odd') ? 'even' : 'odd';
-        $(this).show();
-      }
-    });
+  if (Drupal.ModuleFilter.activeTab != undefined) {
+    Drupal.ModuleFilter.activeTab.element.removeClass('selected');
   }
 
-  Drupal.ModuleFilter.Tab.prototype.updateEnabling = function(amount) {
-    this.enabling = this.enabling || 0;
-    this.enabling += amount;
-    if (this.enabling == 0) {
-      delete(this.enabling);
-    }
+  Drupal.ModuleFilter.activeTab = Drupal.ModuleFilter.tabs[hash + '-tab'];
+  Drupal.ModuleFilter.activeTab.element.addClass('selected');
+
+  var moduleFilter = $('input[name="module_filter[name]"]').data('moduleFilter');
+  var filter = moduleFilter.applyFilter();
+
+  if (!Drupal.ModuleFilter.modulesTop) {
+    Drupal.ModuleFilter.modulesTop = $('#module-filter-modules').offset().top;
+  }
+  else {
+    // Calculate header offset; this is important in case the site is using
+    // admin_menu module which has fixed positioning and is on top of everything
+    // else.
+    var headerOffset = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0;
+    // Scroll back to top of #module-filter-modules.
+    $('html, body').animate({
+      scrollTop: Drupal.ModuleFilter.modulesTop - headerOffset
+    }, 500);
+    // $('html, body').scrollTop(Drupal.ModuleFilter.modulesTop);
   }
 
-  Drupal.ModuleFilter.Tab.prototype.updateDisabling = function(amount) {
-    this.disabling = this.disabling || 0;
-    this.disabling += amount;
-    if (this.disabling == 0) {
-      delete(this.disabling);
-    }
+  Drupal.ModuleFilter.setState('activeTab', hash);
+};
+
+Drupal.ModuleFilter.eventHandlerOperateByURLFragment = function(event) {
+  var hash = $.param.fragment();
+  Drupal.ModuleFilter.selectTab(hash);
+};
+
+Drupal.ModuleFilter.countSummary = function(id) {
+  return Drupal.t('@enabled of @total', { '@enabled': Drupal.settings.moduleFilter.enabledCounts[id].enabled, '@total': Drupal.settings.moduleFilter.enabledCounts[id].total });
+};
+
+Drupal.ModuleFilter.Tab.prototype.updateEnabling = function(name, remove) {
+  this.enabling = this.enabling || {};
+  if (!remove) {
+    this.enabling[name] = name;
+  }
+  else {
+    delete this.enabling[name];
   }
+};
 
-  Drupal.ModuleFilter.Tab.prototype.updateVisualAid = function() {
-    var visualAid = '';
-    if (this.enabling != undefined) {
-      visualAid += '<span class="enabling">' + Drupal.t('+@count', { '@count': this.enabling }) + '</span>';
+Drupal.ModuleFilter.Tab.prototype.updateDisabling = function(name, remove) {
+  this.disabling = this.disabling || {};
+  if (!remove) {
+    this.disabling[name] = name;
+  }
+  else {
+    delete this.disabling[name];
+  }
+};
+
+Drupal.ModuleFilter.Tab.prototype.updateVisualAid = function() {
+  var visualAid = '';
+  var enabling = new Array();
+  var disabling = new Array();
+
+  if (this.enabling != undefined) {
+    for (var i in this.enabling) {
+      enabling.push(this.enabling[i]);
     }
-    if (this.disabling != undefined) {
-      visualAid += '<span class="disabling">' + Drupal.t('-@count', { '@count': this.disabling }) + '</span>';
+    if (enabling.length > 0) {
+      enabling.sort();
+      visualAid += '<span class="enabling">+' + enabling.join('</span>, <span class="enabling">') + '</span>';
     }
-
-    if (!$('span.visual-aid', $(this.element)).size() && visualAid != '') {
-      $(this.element).prepend('<span class="visual-aid"></span>');
+  }
+  if (this.disabling != undefined) {
+    for (var i in this.disabling) {
+      disabling.push(this.disabling[i]);
+    }
+    if (disabling.length > 0) {
+      disabling.sort();
+      if (enabling.length > 0) {
+        visualAid += '<br />';
+      }
+      visualAid += '<span class="disabling">-' + disabling.join('</span>, <span class="disabling">') + '</span>';
     }
+  }
 
-    $('span.visual-aid', $(this.element)).empty().append(visualAid);
+  if (this.visualAid == undefined) {
+    $('a span.summary', this.element).append('<span class="visual-aid"></span>');
+    this.visualAid = $('span.visual-aid', this.element);
   }
 
-  Drupal.ModuleFilter.updateVisualAid = function(type, row) {
-    // Find row class.
-    var classes = row.attr('class').split(' ');
+  this.visualAid.empty().append(visualAid);
+};
+
+Drupal.ModuleFilter.getTabID = function($row) {
+  var id = $row.data('moduleFilterTabID');
+  if (!id) {
+    // Find the tab ID.
+    var classes = $row.attr('class').split(' ');
     for (var i in classes) {
-      // Remove '-content' so we can use as id.
-      var id = classes[i].substring(0, classes[i].length - 8);
-      if (Drupal.ModuleFilter.tabs[id] != undefined) {
+      if (Drupal.ModuleFilter.tabs[classes[i]] != undefined) {
+        id = classes[i];
         break;
       }
     }
+    $row.data('moduleFilterTabID', id);
+  }
+  return id;
+};
 
-    if (Drupal.ModuleFilter.activeTab.id == 'all-tab') {
-      var allTab = Drupal.ModuleFilter.activeTab;
-      var projectTab = Drupal.ModuleFilter.tabs[id];
-    }
-    else {
-      var allTab = Drupal.ModuleFilter.tabs['all-tab'];
-      var projectTab = Drupal.ModuleFilter.activeTab;
-    }
+Drupal.ModuleFilter.updateVisualAid = function(type, $row) {
+  var id = Drupal.ModuleFilter.getTabID($row);
 
-    var name = $('td label strong', row).text();
-    switch (type) {
-      case 'enable':
-        if (Drupal.ModuleFilter.disabling[id + name] != undefined) {
-          delete(Drupal.ModuleFilter.disabling[id + name]);
-          allTab.updateDisabling(-1);
-          projectTab.updateDisabling(-1);
-          row.removeClass('disabling');
-        }
-        else {
-          Drupal.ModuleFilter.enabling[id + name] = name;
-          allTab.updateEnabling(1);
-          projectTab.updateEnabling(1);
-          row.addClass('enabling');
-        }
-        break;
-      case 'disable':
-        if (Drupal.ModuleFilter.enabling[id + name] != undefined) {
-          delete(Drupal.ModuleFilter.enabling[id + name]);
-          allTab.updateEnabling(-1);
-          projectTab.updateEnabling(-1);
-          row.removeClass('enabling');
-        }
-        else {
-          Drupal.ModuleFilter.disabling[id + name] = name;
-          allTab.updateDisabling(1);
-          projectTab.updateDisabling(1);
-          row.addClass('disabling');
-        }
-        break;
-    }
+  if (!id) {
+    return false;
+  }
 
-    allTab.updateVisualAid();
-    projectTab.updateVisualAid();
+  var tab = Drupal.ModuleFilter.tabs[id];
+  var name = $('td:nth(1) strong', $row).text();
+  switch (type) {
+    case 'enable':
+      if (Drupal.ModuleFilter.disabling[id + name] != undefined) {
+        delete Drupal.ModuleFilter.disabling[id + name];
+        tab.updateDisabling(name, true);
+        $row.removeClass('disabling');
+      }
+      else {
+        Drupal.ModuleFilter.enabling[id + name] = name;
+        tab.updateEnabling(name);
+        $row.addClass('enabling');
+      }
+      break;
+    case 'disable':
+      if (Drupal.ModuleFilter.enabling[id + name] != undefined) {
+        delete Drupal.ModuleFilter.enabling[id + name];
+        tab.updateEnabling(name, true);
+        $row.removeClass('enabling');
+      }
+      else {
+        Drupal.ModuleFilter.disabling[id + name] = name;
+        tab.updateDisabling(name);
+        $row.addClass('disabling');
+      }
+      break;
   }
+
+  tab.updateVisualAid();
+};
+
+Drupal.ModuleFilter.Filter.prototype.adjustHeight = function() {
+  // Hack for adjusting the height of the modules section.
+  var minHeight = $('#module-filter-tabs ul').height() + 10;
+  minHeight += $('#module-filter-tabs #module-filter-submit').height();
+  $('#module-filter-modules').css('min-height', minHeight);
+  this.element.trigger('moduleFilter:adjustHeight');
+}
+
 })(jQuery);

+ 176 - 0
sites/all/modules/contrib/admin/module_filter/js/modules.js

@@ -0,0 +1,176 @@
+(function($) {
+
+Drupal.behaviors.moduleFilter = {
+  attach: function(context) {
+    $('#system-modules td.description').once('description', function() {
+      $('.inner.expand', $(this)).click(function() {
+        $(this).toggleClass('expanded');
+      });
+    });
+
+    $('.module-filter-inputs-wrapper', context).once('module-filter', function() {
+      var filterInput = $('input[name="module_filter[name]"]', context);
+      var selector = '#system-modules table tbody tr';
+      if (Drupal.settings.moduleFilter.tabs) {
+        selector += '.module';
+      }
+
+      filterInput.moduleFilter(selector, {
+        wrapper: $('#module-filter-modules'),
+        delay: 500,
+        striping: true,
+        childSelector: 'td:nth(1)',
+        rules: [
+          function(moduleFilter, item) {
+            if (!item.unavailable) {
+              if (moduleFilter.options.showEnabled) {
+                if (item.status && !item.disabled) {
+                  return true;
+                }
+              }
+              if (moduleFilter.options.showDisabled) {
+                if (!item.status && !item.disabled) {
+                  return true;
+                }
+              }
+              if (moduleFilter.options.showRequired) {
+                if (item.status && item.disabled) {
+                  return true;
+                }
+              }
+            }
+            if (moduleFilter.options.showUnavailable) {
+              if (item.unavailable || (!item.status && item.disabled)) {
+                return true;
+              }
+            }
+            return false;
+          }
+        ],
+        buildIndex: [
+          function(moduleFilter, item) {
+            var $checkbox = $('td.checkbox :checkbox', item.element);
+            if ($checkbox.size() > 0) {
+              item.status = $checkbox.is(':checked');
+              item.disabled = $checkbox.is(':disabled');
+            }
+            else {
+              item.status = false;
+              item.disabled = true;
+              item.unavailable = true;
+            }
+            return item;
+          }
+        ],
+        showEnabled: $('#edit-module-filter-show-enabled').is(':checked'),
+        showDisabled: $('#edit-module-filter-show-disabled').is(':checked'),
+        showRequired: $('#edit-module-filter-show-required').is(':checked'),
+        showUnavailable: $('#edit-module-filter-show-unavailable').is(':checked')
+      });
+
+      var moduleFilter = filterInput.data('moduleFilter');
+
+      moduleFilter.operators = {
+        description: function(string, moduleFilter, item) {
+          if (item.description == undefined) {
+            var description = $('.description', item.element).clone();
+            $('.admin-requirements', description).remove();
+            $('.admin-operations', description).remove();
+            item.description = description.text().toLowerCase();
+          }
+
+          if (item.description.indexOf(string) >= 0) {
+            return true;
+          }
+        },
+        requiredBy: function(string, moduleFilter, item) {
+          if (item.requiredBy == undefined) {
+            var requirements = Drupal.ModuleFilter.getRequirements(item.element);
+            item.requires = requirements.requires;
+            item.requiredBy = requirements.requiredBy;
+          }
+
+          for (var i in item.requiredBy) {
+            if (item.requiredBy[i].indexOf(string) >= 0) {
+              return true;
+            }
+          }
+        },
+        requires: function(string, moduleFilter, item) {
+          if (item.requires == undefined) {
+            var requirements = Drupal.ModuleFilter.getRequirements(item.element);
+            item.requires = requirements.requires;
+            item.requiredBy = requirements.requiredBy;
+          }
+
+          for (var i in item.requires) {
+            if (item.requires[i].indexOf(string) >= 0) {
+              return true;
+            }
+          }
+        }
+      };
+
+      $('#edit-module-filter-show-enabled', context).change(function() {
+        moduleFilter.options.showEnabled = $(this).is(':checked');
+        moduleFilter.applyFilter();
+      });
+      $('#edit-module-filter-show-disabled', context).change(function() {
+        moduleFilter.options.showDisabled = $(this).is(':checked');
+        moduleFilter.applyFilter();
+      });
+      $('#edit-module-filter-show-required', context).change(function() {
+        moduleFilter.options.showRequired = $(this).is(':checked');
+        moduleFilter.applyFilter();
+      });
+      $('#edit-module-filter-show-unavailable', context).change(function() {
+        moduleFilter.options.showUnavailable = $(this).is(':checked');
+        moduleFilter.applyFilter();
+      });
+
+      if (!Drupal.settings.moduleFilter.tabs) {
+        moduleFilter.element.bind('moduleFilter:start', function() {
+          $('#system-modules fieldset').show();
+        });
+
+        moduleFilter.element.bind('moduleFilter:finish', function(e, data) {
+          $('#system-modules fieldset').each(function(i) {
+            $fieldset = $(this);
+            if ($('tbody tr', $fieldset).filter(':visible').length == 0) {
+              $fieldset.hide();
+            }
+          });
+        });
+
+        moduleFilter.applyFilter();
+      }
+    });
+  }
+};
+
+Drupal.ModuleFilter.getRequirements = function(element) {
+  var requires = new Array();
+  var requiredBy = new Array();
+  $('.admin-requirements', element).each(function() {
+    var text = $(this).text();
+    if (text.substr(0, 9) == 'Requires:') {
+      // Requires element.
+      requiresString = text.substr(9);
+      requires = requiresString.replace(/\([a-z]*\)/g, '').split(',');
+    }
+    else if (text.substr(0, 12) == 'Required by:') {
+      // Required by element.
+      requiredByString = text.substr(12);
+      requiredBy = requiredByString.replace(/\([a-z]*\)/g, '').split(',');
+    }
+  });
+  for (var i in requires) {
+    requires[i] = $.trim(requires[i].toLowerCase());
+  }
+  for (var i in requiredBy) {
+    requiredBy[i] = $.trim(requiredBy[i].toLowerCase());
+  }
+  return { requires: requires, requiredBy: requiredBy };
+};
+
+})(jQuery);

+ 67 - 0
sites/all/modules/contrib/admin/module_filter/js/permissions.js

@@ -0,0 +1,67 @@
+(function($) {
+
+var lastModuleItem;
+
+Drupal.behaviors.moduleFilterPermissions = {
+  attach: function(context) {
+    $('.module-filter-inputs-wrapper', context).once('module-filter', function() {
+      var filterInput = $('input[name="module_filter[name]"]', context);
+      var selector = '#permissions tbody tr';
+
+      // Move location of filter input.
+      $('#permissions').parent().prepend(filterInput.parent().parent());
+
+      filterInput.moduleFilter(selector, {
+        wrapper: $('#permissions').parent(),
+        childSelector: 'td.module',
+        buildIndex: [
+          function(moduleFilter, item) {
+            item.isModule = (item.text != '') ? true : false;
+            if (item.isModule) {
+              item.children = new Array();
+              lastModuleItem = item;
+            }
+            else {
+              item.parent = lastModuleItem;
+              lastModuleItem.children.push(item);
+            }
+            return item;
+          }
+        ]
+      });
+
+      var moduleFilter = filterInput.data('moduleFilter');
+
+      moduleFilter.operators = {
+        perm: function(string, moduleFilter, item) {
+          if (!item.isModule) {
+            if (item.name == undefined) {
+              var $name = $('td.permission', item.element).clone();
+              $('.description', $name).remove();
+              item.name = $name.text().trim().toLowerCase();
+            }
+
+            if (item.name.indexOf(string) >= 0) {
+              return true;
+            }
+          }
+        }
+      };
+
+      moduleFilter.element.bind('moduleFilter:finish', function(e, data) {
+        for (var i in moduleFilter.results) {
+          if (moduleFilter.results[i].isModule) {
+            for (var k in moduleFilter.results[i].children) {
+              moduleFilter.results[i].children[k].element.removeClass('js-hide');
+            }
+          }
+          else {
+            moduleFilter.results[i].parent.element.removeClass('js-hide');
+          }
+        }
+      });
+    });
+  }
+};
+
+})(jQuery);

+ 117 - 0
sites/all/modules/contrib/admin/module_filter/js/update_status.js

@@ -0,0 +1,117 @@
+(function($) {
+
+Drupal.behaviors.moduleFilterUpdateStatus = {
+  attach: function(context) {
+    $('#module-filter-update-status-form').once('update-status', function() {
+      var filterInput = $('input[name="module_filter[name]"]', context);
+      filterInput.moduleFilter('table.update > tbody > tr', {
+        wrapper: $('table.update:first').parent(),
+        delay: 300,
+        childSelector: 'div.project a',
+        rules: [
+          function(moduleFilter, item) {
+            switch (moduleFilter.options.show) {
+              case 'all':
+                return true;
+              case 'updates':
+                if (item.state == 'warning' || item.state == 'error') {
+                  return true;
+                }
+                break;
+              case 'security':
+                if (item.state == 'error') {
+                  return true;
+                }
+                break;
+              case 'ignore':
+                if (item.state == 'ignored') {
+                  return true;
+                }
+                break;
+              case 'unknown':
+                if (item.state == 'unknown') {
+                  return true;
+                }
+                break;
+            }
+            return false;
+          }
+        ],
+        buildIndex: [
+          function(moduleFilter, item) {
+            if ($('.version-status', item.element).text() == Drupal.t('Ignored from settings')) {
+              item.state = 'ignored';
+              return item;
+            }
+            if (item.element.is('.ok')) {
+              item.state = 'ok';
+            }
+            else if (item.element.is('.warning')) {
+              item.state = 'warning';
+            }
+            else if (item.element.is('.error')) {
+              item.state = 'error';
+            }
+            else if (item.element.is('.unknown')) {
+              item.state = 'unknown';
+            }
+            return item;
+          }
+        ],
+        show: $('#edit-module-filter-show input[name="module_filter[show]"]', context).val()
+      });
+
+      var moduleFilter = filterInput.data('moduleFilter');
+
+      if (Drupal.settings.moduleFilter.rememberUpdateState) {
+        var updateShow = Drupal.ModuleFilter.getState('updateShow');
+        if (updateShow) {
+          moduleFilter.options.show = updateShow;
+          $('#edit-module-filter-show input[name="module_filter[show]"][value="' + updateShow + '"]', context).click();
+        }
+      }
+
+      $('#edit-module-filter-show input[name="module_filter[show]"]', context).change(function() {
+        moduleFilter.options.show = $(this).val();
+        Drupal.ModuleFilter.setState('updateShow', moduleFilter.options.show);
+        moduleFilter.applyFilter();
+      });
+
+      moduleFilter.element.bind('moduleFilter:start', function() {
+        $('table.update').each(function() {
+          $(this).show().prev('h3').show();
+        });
+      });
+
+      moduleFilter.element.bind('moduleFilter:finish', function(e, data) {
+        $('table.update').each(function() {
+          var $table = $(this);
+          if ($('tbody tr', $(this)).filter(':visible').length == 0) {
+            $table.hide().prev('h3').hide();
+          }
+        });
+      });
+
+      moduleFilter.element.bind('moduleFilter:keyup', function() {
+        if (moduleFilter.clearOffset == undefined) {
+          moduleFilter.inputWidth = filterInput.width();
+          moduleFilter.clearOffset = moduleFilter.element.parent().find('.module-filter-clear a').width();
+        }
+        if (moduleFilter.text) {
+          filterInput.width(moduleFilter.inputWidth - moduleFilter.clearOffset - 5).parent().css('margin-right', moduleFilter.clearOffset + 5);
+        }
+        else {
+          filterInput.width(moduleFilter.inputWidth).parent().css('margin-right', 0);
+        }
+      });
+
+      moduleFilter.element.parent().find('.module-filter-clear a').click(function() {
+        filterInput.width(moduleFilter.inputWidth).parent().css('margin-right', 0);
+      });
+
+      moduleFilter.applyFilter();
+    });
+  }
+};
+
+})(jQuery);

+ 62 - 7
sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc

@@ -14,10 +14,17 @@
  * Settings form for module filter.
  */
 function module_filter_settings() {
+  $form['module_filter_set_focus'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Set focus to filter field on page load'),
+    '#description' => t('Currently has no effect when using Overlay module.'),
+    '#default_value' => variable_get('module_filter_set_focus', 1),
+  );
+
   $form['module_filter_tabs'] = array(
     '#type' => 'checkbox',
-    '#title' => t('Tabs'),
-    '#description' => t('Divide module groups into tabbed list.'),
+    '#title' => t('Enhance the modules page with tabs'),
+    '#description' => t('Alternate tabbed theme that restructures packages into tabs.'),
     '#default_value' => variable_get('module_filter_tabs', 1)
   );
   $form['tabs'] = array(
@@ -35,15 +42,63 @@ function module_filter_settings() {
   );
   $form['tabs']['module_filter_visual_aid'] = array(
     '#type' => 'checkbox',
-    '#title' => t('Visuals for newly enabled and disabled modules'),
-    '#description' => t("Adds a basic count to tabs of modules being enabled/disabled and colors the module row pending it's being enabled or disabled"),
+    '#title' => t('Visual aids'),
+    '#description' => t('When enabling/disabling modules, the module name will display in the tab summary.<br />When filtering, a count of results for each tab will be presented.'),
     '#default_value' => variable_get('module_filter_visual_aid', 1)
   );
+  $form['tabs']['module_filter_hide_empty_tabs'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Hide tabs with no results'),
+    '#description' => t('When a filter returns no results for a tab, the tab is hidden. This is dependent on visual aids being enabled.'),
+    '#default_value' => variable_get('module_filter_hide_empty_tabs', 0)
+  );
   $form['tabs']['module_filter_dynamic_save_position'] = array(
     '#type' => 'checkbox',
-    '#title' => t('Dynamically position save button'),
-    '#description' => t("DEVELOPMENTAL: For sites with lots of tabs, enable to help keep the 'Save configuration' button more accessible."),
-    '#default_value' => variable_get('module_filter_dynamic_save_position', 0)
+    '#title' => t('Dynamically position Save button'),
+    '#description' => t("For sites with lots of tabs, enable to help keep the 'Save configuration' button more accessible."),
+    '#default_value' => variable_get('module_filter_dynamic_save_position', 1)
+  );
+  $form['tabs']['module_filter_use_url_fragment'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use URL fragment'),
+    '#description' => t('Use URL fragment when navigating between tabs. This lets you use the browsers back/forward buttons to navigate through the tabs you selected.') . '<br />' . t('When the Overlay module is enabled this functionality will not be used since overlay relies on the URL fragment.'),
+    '#default_value' => variable_get('module_filter_use_url_fragment', 1)
+  );
+  $form['tabs']['module_filter_use_switch'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use switch instead of checkbox'),
+    '#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['tabs']['module_filter_remember_active_tab'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Remember active tab.'),
+    '#description' => t('When enabled, the active tab will be remembered.'),
+    '#default_value' => variable_get('module_filter_remember_active_tab', 1),
   );
+
+  $form['update'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Update status'),
+    '#collapsible' => TRUE,
+    '#collapsed' => (module_exists('update')) ? FALSE : TRUE,
+  );
+  $form['update']['module_filter_remember_update_state'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Remember the last selected filter.'),
+    '#description' => t('When enabled, the last state (All, Update available, Security update, Unknown) will be remembered.'),
+    '#default_value' => variable_get('module_filter_remember_update_state', 0),
+  );
+
+  if (module_exists('page_actions')) {
+    $form['tabs']['module_filter_dynamic_save_position']['#description'] .= '<br />' . t('The module %name is enabled and thus this setting will have no affect.', array('%name' => t('Page actions')));
+  }
+
   return system_settings_form($form);
 }

+ 4 - 3
sites/all/modules/contrib/admin/module_filter/module_filter.info

@@ -1,6 +1,7 @@
 name = Module filter
 description = "Filter the modules list."
 core = 7.x
+package = Administration
 
 files[] = module_filter.install
 files[] = module_filter.js
@@ -16,9 +17,9 @@ files[] = js/module_filter_tab.js
 
 configure = admin/config/user-interface/modulefilter
 
-; Information added by drupal.org packaging script on 2013-08-08
-version = "7.x-1.8"
+; Information added by Drupal.org packaging script on 2015-02-22
+version = "7.x-2.0"
 core = "7.x"
 project = "module_filter"
-datestamp = "1375995220"
+datestamp = "1424631189"
 

+ 26 - 1
sites/all/modules/contrib/admin/module_filter/module_filter.install

@@ -5,13 +5,20 @@
  */
 
 /**
- * Implementation of hook_uninstall().
+ * Implements hook_uninstall().
  */
 function module_filter_uninstall() {
+  variable_del('module_filter_set_focus');
   variable_del('module_filter_tabs');
   variable_del('module_filter_count_enabled');
   variable_del('module_filter_visual_aid');
+  variable_del('module_filter_hide_empty_tabs');
   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_active_tab');
+  variable_del('module_filter_remember_update_state');
 }
 
 /**
@@ -20,3 +27,21 @@ function module_filter_uninstall() {
 function module_filter_update_7100() {
   variable_del('module_filter_autocomplete');
 }
+
+/**
+ * Rebuild the menu and theme registry.
+ */
+function module_filter_update_7200() {
+  menu_rebuild();
+  system_rebuild_theme_data();
+  drupal_theme_rebuild();
+}
+
+/**
+ * Old update that use to remove the module_filter_dynamic_save_position variable.
+ */
+function module_filter_update_7201() {
+  // We don't want to remove this update hook but at the same time we no
+  // longer want to lose the variable setting, so we just comment it out.
+  // variable_del('module_filter_dynamic_save_position');
+}

+ 172 - 80
sites/all/modules/contrib/admin/module_filter/module_filter.module

@@ -12,7 +12,7 @@
  */
 
 /**
- * Implementation of hook_perm().
+ * Implements hook_perm().
  */
 function module_filter_permission() {
   return array(
@@ -24,12 +24,12 @@ function module_filter_permission() {
 }
 
 /**
- * Implementation of hook_menu().
+ * Implements hook_menu().
  */
 function module_filter_menu() {
   $items['admin/config/user-interface/modulefilter'] = array(
     'title' => 'Module filter',
-    'description' => 'Configure settings for Module Filter.',
+    'description' => 'Configure how the modules page looks and acts.',
     'access arguments' => array('administer module filter'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('module_filter_settings'),
@@ -39,7 +39,19 @@ function module_filter_menu() {
 }
 
 /**
- * Implementation of hook_form_FORM_ID_alter().
+ * Implements hook_menu_alter().
+ */
+function module_filter_menu_alter(&$items) {
+  if (isset($items['admin/reports/updates'])) {
+    // We route the updates report page through us.
+    $items['admin/reports/updates']['page callback'] = 'module_filter_update_status';
+    $items['admin/reports/updates']['file'] = 'module_filter.pages.inc';
+    $items['admin/reports/updates']['module'] = 'module_filter';
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
  */
 function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id) {
   // Don't alter the form when confirming.
@@ -48,22 +60,22 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id)
   }
 
   $form['module_filter'] = array(
-    '#tree' => TRUE,
-    '#weight' => -1,
+    '#type' => 'module_filter',
     '#attached' => array(
-      'css' => array(
-        drupal_get_path('module', 'module_filter') .'/css/module_filter.css',
-      ),
-    ),
+      'js' => array(
+        drupal_get_path('module', 'module_filter') . '/js/modules.js'
+      )
+    )
   );
-  $form['module_filter']['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Filter list')
+  $checkbox_defaults = array(
+    ((isset($_GET['enabled'])) ? $_GET['enabled'] : 1) ? 'enabled' : '',
+    ((isset($_GET['disabled'])) ? $_GET['disabled'] : 1) ? 'disabled' : '',
+    ((isset($_GET['required'])) ? $_GET['required'] : 1) ? 'required' : '',
+    ((isset($_GET['unavailable'])) ? $_GET['unavailable'] : 1) ? 'unavailable' : ''
   );
-
   $form['module_filter']['show'] = array(
     '#type' => 'checkboxes',
-    '#default_value' => array('enabled', 'disabled', 'required', 'unavailable'),
+    '#default_value' => array_filter($checkbox_defaults),
     '#options' => array('enabled' => t('Enabled'), 'disabled' => t('Disabled'), 'required' => t('Required'), 'unavailable' => t('Unavailable')),
     '#prefix' => '<div id="module-filter-show-wrapper">',
     '#suffix' => '</div>'
@@ -71,98 +83,178 @@ function module_filter_form_system_modules_alter(&$form, &$form_state, $form_id)
 
   if (variable_get('module_filter_tabs', 1)) {
     $form['module_filter']['#attached']['css'][] = drupal_get_path('module', 'module_filter') .'/css/module_filter_tab.css';
+    $form['module_filter']['#attached']['library'][] = array('system', 'jquery.bbq');
     $form['module_filter']['#attached']['js'][] = drupal_get_path('module', 'module_filter') .'/js/module_filter_tab.js';
-    $form['module_filter']['#attached']['js'][] = array(
-      'data' => array('moduleFilter' => array('visualAid' => variable_get('module_filter_visual_aid', 1))),
-      'type' => 'setting',
-    );
 
-    if (variable_get('module_filter_dynamic_save_position', 0)) {
+    if (!module_exists('page_actions') && variable_get('module_filter_dynamic_save_position', 1)) {
+      $form['module_filter']['#attached']['css'][] = drupal_get_path('module', 'module_filter') .'/css/dynamic_position.css';
       $form['module_filter']['#attached']['js'][] = drupal_get_path('module', 'module_filter') .'/js/dynamic_position.js';
     }
 
-    $form['module_filter']['#size'] = 45;
-
-    // Remove the fieldsets for each package since we will be using tabs
-    // instead. Put all modules into one array.
-    $modules = array(
-      '#theme' => 'module_filter_modules_table',
-      '#header' => array(
-        array('data' => t('Enabled'), 'class' => 'checkbox'),
-        t('Name'),
-        t('Version'),
-        t('Description'),
-        array('data' => t('Operations'), 'colspan' => 3)
-      )
-    );
+    $form['#attached']['css'][] = drupal_get_path('module', 'module_filter') . '/css/modules.css';
 
-    $all = t('All');
-    $tab_counts = array($all => array('id' => 'all', 'enabled' => 0, 'total' => 0));
-    $form['#packages'] = array();
-    foreach (element_children($form['modules']) as $package) {
-      // Add the package to $form['#packages']. Tabs are built from this.
-      $form['#packages'][$package] = $package;
-
-      if (!isset($tab_counts[$package])) {
-        $tab_counts[$package] = array('enabled' => 0, 'total' => 0);
-      }
-
-      foreach (element_children($form['modules'][$package]) as $module) {
-        $tab_counts[$all]['total']++;
-        $tab_counts[$package]['total']++;
-        if (!empty($form['modules'][$package][$module]['enable']['#default_value'])) {
-          $tab_counts[$all]['enabled']++;
-          $tab_counts[$package]['enabled']++;
-        }
-
-        $modules[$module] = $form['modules'][$package][$module];
-        $modules[$module]['#package'] = $package;
-        $modules[$module]['#parents'] = array('modules', $package, $module);
-      }
-    }
+    $form['#theme'] = 'module_filter_system_modules_tabs';
+  }
 
-    // Sort the array of modules alphabetically.
-    uasort($modules, 'module_filter_sort_modules_by_display_name');
+  $form['#submit'][] = 'module_filter_system_modules_submit_redirect';
 
-    // Replace the $form['modules'] with our $modules array.
-    $form['modules'] = $modules;
+  if (variable_get('module_filter_track_recent_modules', 1)) {
+    $form['#submit'][] = 'module_filter_system_modules_submit_recent';
+  }
+}
 
-    // Add our $tab_counts array to the form.
-    $form['#tab_counts'] = $tab_counts;
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+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',
+      ),
+    ),
+    '#weight' => -100,
+  );
+}
 
-    $form['#theme'] = 'module_filter_system_modules_tabs';
-  }
-  else {
-    $form['module_filter']['#attached']['js'][] = drupal_get_path('module', 'module_filter') .'/js/module_filter.js';
-    $form['module_filter']['#prefix'] = '<div id="module-filter-wrapper" style="display: none;">';
-    $form['module_filter']['#suffix'] = '</div>';
-  }
+/**
+ * Implements hook_element_info().
+ */
+function module_filter_element_info() {
+  $types['module_filter'] = array(
+    '#input' => TRUE,
+    '#process' => array('form_process_module_filter', 'ajax_process_form'),
+    '#weight' => -1,
+    '#tree' => TRUE,
+    '#theme' => 'module_filter'
+  );
+  return $types;
 }
 
 /**
- * Implementation of hook_theme().
+ * Implements hook_theme().
  */
 function module_filter_theme() {
   return array(
-    'module_filter_modules_table' => array(
-      'render element' => 'form',
+    'module_filter' => array(
+      'render element' => 'element',
       'file' => 'module_filter.theme.inc',
     ),
     'module_filter_system_modules_tabs' => array(
       'render element' => 'form',
-      'file' => 'module_filter.theme.inc'
+      'file' => 'module_filter.theme.inc',
+    ),
+    'module_filter_operations' => array(
+      'variables' => array('links' => array(), 'dropbutton' => FALSE),
+      'file' => 'module_filter.theme.inc',
+    ),
+  );
+}
+
+function form_process_module_filter($element, &$form_state) {
+  $element['name'] = array(
+    '#type' => 'textfield',
+    '#title' => (isset($element['#title'])) ? $element['#title'] : t('Filter list'),
+    '#default_value' => (isset($element['#default_value'])) ? $element['#default_value'] : ((isset($_GET['filter'])) ? $_GET['filter'] : ''),
+    '#size' => (isset($element['#size'])) ? $element['#size'] : 45,
+    '#weight' => (isset($element['#weight'])) ? $element['#weight'] : -10,
+    '#attributes' => ((isset($element['#attributes'])) ? $element['#attributes'] : array()) + array('autocomplete' => 'off'),
+    '#attached' => array(
+      'css' => array(
+        drupal_get_path('module', 'module_filter') . '/css/module_filter.css'
+      ),
+      'js' => array(
+        'misc/jquery.cookie.js',
+        drupal_get_path('module', 'module_filter') . '/js/module_filter.js',
+        array(
+          'data' => array(
+            'moduleFilter' => array(
+              'setFocus' => variable_get('module_filter_set_focus', 1),
+              'tabs' => variable_get('module_filter_tabs', 1),
+              'countEnabled' => variable_get('module_filter_count_enabled', 1),
+              'visualAid' => variable_get('module_filter_visual_aid', 1),
+              'hideEmptyTabs' => variable_get('module_filter_hide_empty_tabs', 0),
+              '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),
+              'rememberActiveTab' => variable_get('module_filter_remember_active_tab', 1),
+              'rememberUpdateState' => variable_get('module_filter_remember_update_state', 0),
+            )
+          ),
+          'type' => 'setting'
+        )
+      )
     )
   );
+  if (isset($element['#description'])) {
+    $element['name']['#description'] = $element['#description'];
+  }
+  return $element;
 }
 
-function module_filter_sort_modules_by_display_name($a, $b) {
-  if (is_array($a) && is_array($b) && isset($a['#package'], $b['#package'])) {
-    return strcasecmp($a['name']['#markup'], $b['name']['#markup']);
+function module_filter_system_modules_submit_redirect($form, &$form_state) {
+  $query = array();
+  if (!empty($form_state['values']['module_filter']['name'])) {
+    $query['filter'] = $form_state['values']['module_filter']['name'];
   }
-  return 0;
+  $query['enabled'] = (int)(!empty($form_state['values']['module_filter']['show']['enabled']));
+  $query['disabled'] = (int)(!empty($form_state['values']['module_filter']['show']['disabled']));
+  $query['required'] = (int)(!empty($form_state['values']['module_filter']['show']['required']));
+  $query['unavailable'] = (int)(!empty($form_state['values']['module_filter']['show']['unavailable']));
+
+  $form_state['redirect'] = array(
+    'admin/modules',
+    array('query' => $query),
+  );
+}
+
+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();
+
+  // Remove hidden modules from display list.
+  $visible_files = $files;
+  foreach ($visible_files as $filename => $file) {
+    if (!empty($file->info['hidden'])) {
+      unset($visible_files[$filename]);
+    }
+  }
+
+  uasort($visible_files, 'system_sort_modules_by_info_name');
+
+  $new_modules = array();
+  foreach ($visible_files as $filename => $module) {
+    $ctime = filectime(dirname($module->uri) . '/' . $module->name . '.info');
+    if (($ctime - strtotime('-1 week')) > 0) {
+      $new_modules[$filename] = module_filter_get_id($filename);
+    }
+  }
+  return $new_modules;
 }
 
 function module_filter_get_id($text) {
   $id = strtolower($text);
-  return preg_replace('/([^a-z])([\/(  )])*/', '-', $id);
+  $id = preg_replace('/([^a-z0-9]+)/', '-', $id);
+  return trim($id, '-');
+}
+
+function module_filter_recent_filter($var) {
+  return (!($var < REQUEST_TIME - 60*60*24*7));
 }

+ 46 - 0
sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc

@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Wrapper function for update_status().
+ *
+ * @see update_status().
+ */
+function module_filter_update_status() {
+  module_load_include('inc', 'update', 'update.report');
+  $update_report = update_status();
+
+  return array(
+    'module_filter' => drupal_get_form('module_filter_update_status_form'),
+    'update_report' => array(
+      '#markup' => $update_report
+    )
+  );
+}
+
+function module_filter_update_status_form($form, &$form_state) {
+  $form['module_filter'] = array(
+    '#type' => 'module_filter',
+    '#attached' => array(
+      'css' => array(
+        drupal_get_path('module', 'module_filter') . '/css/update_status.css'
+      ),
+      'js' => array(
+        drupal_get_path('module', 'module_filter') . '/js/update_status.js'
+      ),
+    ),
+  );
+  $form['module_filter']['show'] = array(
+    '#type' => 'radios',
+    '#default_value' => (isset($_GET['show']) && in_array($_GET['show'], array('all', 'updates', 'security', 'unknown'))) ? $_GET['show'] : 'all',
+    '#options' => array('all' => t('All'), 'updates' => t('Update available'), 'security' => t('Security update'), 'unknown' => t('Unknown')),
+    '#prefix' => '<div id="module-filter-show-wrapper">',
+    '#suffix' => '</div>'
+  );
+  if (module_exists('update_advanced')) {
+    $options = $form['module_filter']['show']['#options'];
+    $form['module_filter']['show']['#options'] = array_slice($options, 0, 2);
+    $form['module_filter']['show']['#options']['ignore'] = t('Ignored from settings');
+    $form['module_filter']['show']['#options'] = array_merge($form['module_filter']['show']['#options'], array_slice($options, 2));
+  }
+  return $form;
+}

+ 172 - 57
sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc

@@ -6,76 +6,191 @@
  * @author greenSkin
  */
 
-function theme_module_filter_modules_table($variables) {
-  $form = $variables['form'];
-
-  // Individual table headers.
-  $rows = array();
-  // Iterate through all the modules, which are
-  // children of this fieldset.
-  foreach (element_children($form) as $key) {
-    // Stick it into $module for easier accessing.
-    $module = $form[$key];
-    $row = array();
-    unset($module['enable']['#title']);
-    $row[] = array('class' => array('checkbox'), 'data' => drupal_render($module['enable']));
-    $label = '<label';
-    if (isset($module['enable']['#id'])) {
-      $label .= ' for="'. $module['enable']['#id'] .'"';
-    }
-    $row[] = $label .'><strong>' . drupal_render($module['name']) . '</strong></label>';
-    $row[] = drupal_render($module['version']);
-    // Add the description, along with any modules it requires.
-    $description = drupal_render($module['description']);
-    if ($module['#requires']) {
-      $description .= '<div class="admin-requirements">' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '</div>';
-    }
-    if ($module['#required_by']) {
-      $description .= '<div class="admin-requirements">' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '</div>';
-    }
-    $row[] = array('data' => $description, 'class' => array('description'));
-    // Display links (such as help or permissions) in their own columns.
-    foreach (array('help', 'permissions', 'configure') as $key) {
-      $row[] = array('data' => drupal_render($module['links'][$key]), 'class' => array($key));
-    }
-
-    $id = module_filter_get_id($module['#package']);
-    $rows[] = array(
-      'data' => $row,
-      'class' => array($id .'-tab-content')
-    );
-  }
-
-  return theme('table', array('header' => $form['#header'], 'rows' => $rows, 'attributes' => array('class' => array('package'))));
+function theme_module_filter($variables) {
+  $element = $variables['element'];
+  return '<div class="module-filter-inputs-wrapper">' . drupal_render_children($element) . '</div>';
 }
 
 /**
  * Theme callback for the modules tabbed form.
  */
 function theme_module_filter_system_modules_tabs($variables) {
+  if (module_exists('views_ui')) {
+    // Hack to get consistent style with views ctools dropbutton.
+    if (module_load_include('inc', 'views_ui', 'includes/admin')) {
+      foreach (views_ui_get_admin_css() as $file => $options) {
+        drupal_add_css($file, $options);
+      }
+    }
+  }
+
   $form = $variables['form'];
 
-  $count_enabled = variable_get('module_filter_count_enabled', 1);
+  if (!module_exists('page_actions')) {
+    $form['actions']['#prefix'] = '<div id="module-filter-submit">';
+    $form['actions']['#suffix'] = '</div>';
+  }
+
+  $header = array(
+    array('data' => '', 'class' => array('checkbox')),
+    array('data' => t('Name'), 'class' => array('name')),
+    array('data' => t('Description'), 'class' => array('description')),
+    array('data' => t('Links'), 'class' => array('links')),
+  );
+  $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_set('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');
+  foreach (element_children($form['modules']) as $package) {
+    $package_id = module_filter_get_id($package);
+    $package_ids[] = $package_id;
+
+    // Package title and header.
+    $rows[] = array('data' => array(array('data' => '<h3>' . $form['modules'][$package]['#title'] . '</h3>', 'colspan' => 4)), 'id' => $package_id . '-package', 'class' => array('admin-package-title'));
+    $rows[] = array('data' => $header, 'class' => array('admin-package-header'));
+
+    $stripe = 'odd';
+    $enabled[$package_id] = array();
+    foreach (element_children($form['modules'][$package]) as $key) {
+      $module = &$form['modules'][$package][$key];
 
-  // Display packages.
-  $all = t('All');
-  $all_count = ($count_enabled) ? '<span class="counts">' . t('!enabled of !total', array('!enabled' => $form['#tab_counts'][$all]['enabled'], '!total' => $form['#tab_counts'][$all]['total'])) . '</span>' : '';
-  $tabs = array('all' => '<li class="active"><a id="all-tab" class="project-tab overlay-exclude" href="#all">' . $all . $all_count . '</a></li>');
-  foreach ($form['#packages'] as $package) {
-    $id = module_filter_get_id($package);
+      $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;
+      }
 
-    $count = ($count_enabled) ? '<span class="counts">' . t('!enabled of !total', array('!enabled' => $form['#tab_counts'][$package]['enabled'], '!total' => $form['#tab_counts'][$package]['total'])) . '</span>' : '';
-    $tabs[$id] = '<li><a id="' . $id . '-tab" class="project-tab overlay-exclude" href="#' . str_replace('-', '_', $id) . '">' . $package . $count . '</a></li>';
+      $row = array();
+
+      $version = !empty($module['version']['#markup']);
+      $requires = !empty($module['#requires']);
+      $required_by = !empty($module['#required_by']);
+
+      $toggle_enable = '';
+      if (isset($module['enable']['#type']) && $module['enable']['#type'] == 'checkbox') {
+        unset($module['enable']['#title']);
+        $class = ($is_enabled ? 'enabled' : 'off');
+        if (!empty($module['enable']['#disabled'])) {
+          $class .= ' disabled';
+        }
+        $toggle_enable = '<div class="js-hide toggle-enable ' . $class . '"><div>&nbsp;</div></div>';
+      }
+      $row[] = array('class' => array('checkbox'), 'data' => $toggle_enable . drupal_render($module['enable']));
+
+      $label = '<label';
+      if (isset($module['enable']['#id'])) {
+        $label .= ' for="' . $module['enable']['#id'] . '"';
+      }
+      $row[] = array('class' => array('name'), 'data' => $label . '><strong>' . drupal_render($module['name']) . '</strong> <span class="module-machine-name">(' . $key . ')</span></label>');
+
+      // Add the description, along with any modules it requires.
+      $description = '<span class="details"><span class="text">' . drupal_render($module['description']) . '</span></span>';
+      if ($version || $requires || $required_by) {
+        $description .= '<div class="requirements">';
+        if ($version) {
+          $description .= '<div class="admin-requirements">' . t('Version: !module-version', array('!module-version' => drupal_render($module['version']))) . '</div>';
+        }
+        if ($requires) {
+          $description .= '<div class="admin-requirements">' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '</div>';
+        }
+        if ($required_by) {
+          $description .= '<div class="admin-requirements">' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '</div>';
+        }
+        $description .= '</div>';
+      }
+      $row[] = array('data' => '<div class="inner expand" role="button">' . $description . '</div>', 'class' => array('description'));
+
+      $operations = (module_exists('ctools')) ? theme('module_filter_operations', array('links' => $module['links'], 'dropbutton' => TRUE)) : theme('module_filter_operations', array('links' => $module['links']));
+      $row[] = array('data' => '<div class="links">' . $operations . '</div>', 'class' => array('links'));
+
+      $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';
+      }
+      $rows[] = array('data' => $row, 'no_striping' => TRUE, 'class' => $class);
+      $stripe = $flip[$stripe];
+    }
+
+    // Set the package as printed.
+    $form['modules'][$package]['#printed'] = TRUE;
+  }
+
+  if (variable_get('module_filter_count_enabled', 1)) {
+    $enabled_counts = array();
+    foreach ($enabled as $package_id => $value) {
+      $enabled_counts[$package_id] = array(
+        'enabled' => count(array_filter($value)),
+        'total' => count($value),
+      );
+    }
+    drupal_add_js(array(
+      'moduleFilter' => array(
+        'packageIDs' => $package_ids,
+        'enabledCounts' => $enabled_counts,
+      )
+    ), 'setting');
   }
 
+  // Add first and last class to rows.
+  $rows[0]['class'][] = 'first';
+  $rows[count($rows) - 1]['class'][] = 'last';
+
   $output = '<div id="module-filter-wrapper">';
-  $output .= '<div id="module-filter-left">';
-  $output .= '<div id="module-filter-tabs"><ul>'. implode($tabs) . '</ul></div>';
-  $output .= '<div id="module-filter-submit">' . drupal_render($form['actions']) . '</div></div>';
-  $output .= '<div id="module-filter-right"><div id="module-filter-squeeze">' . drupal_render($form['module_filter']);
-  $output .= drupal_render($form['modules']) . '</div></div>';
-  $output .= '<div class="clear-block"></div>';
-  $output .= '</div>';
+  $output .= '<div id="module-filter-modules">' . drupal_render($form['module_filter']);
+  $output .= theme('table', array('header' => $header, 'rows' => $rows));
   $output .= drupal_render_children($form);
+  $output .= '</div>';
+  $output .= '</div>';
   return $output;
 }
+
+function theme_module_filter_operations(&$vars) {
+  $links = &$vars['links'];
+  $dropbutton = $vars['dropbutton'];
+
+  $operations = array();
+  foreach (element_children($links) as $key) {
+    if ($dropbutton) {
+      hide($links[$key]);
+      if (!empty($links[$key]['#href'])) {
+        $operations[] = array(
+          'title' => $links[$key]['#title'],
+          'href' => $links[$key]['#href'],
+        );
+      }
+    }
+    else {
+      $data = drupal_render($links[$key]);
+      if (!empty($data)) {
+        $operations[] = array('data' => $data);
+      }
+    }
+  }
+  if (!empty($operations)) {
+    if ($dropbutton) {
+      return '<div class="admin-operations">' . theme('links__ctools_dropbutton', array('title' => t('Operations'), 'links' => $operations, 'attributes' => array('class' => array('links')))) . '</div>';
+    }
+    return '<div class="admin-operations">' . theme('item_list', array('items' => $operations, 'attributes' => array('class' => array('links', 'inline')))) . '</div>';
+  }
+}

+ 333 - 268
sites/all/modules/contrib/admin/override_node_options/LICENSE.txt

@@ -1,274 +1,339 @@
-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.
+                    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.)
+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
+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
+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.

+ 4 - 3
sites/all/modules/contrib/admin/override_node_options/override_node_options.info

@@ -1,11 +1,12 @@
 name = Override node options
 description = "Allow non-admins to override the default publishing options for nodes they can edit."
 core = 7.x
+package = Permissions
 files[] = override_node_options.test
 
-; Information added by drupal.org packaging script on 2011-05-06
-version = "7.x-1.12"
+; Information added by Drupal.org packaging script on 2014-09-19
+version = "7.x-1.13"
 core = "7.x"
 project = "override_node_options"
-datestamp = "1304695316"
+datestamp = "1411157931"
 

+ 22 - 0
sites/all/modules/contrib/admin/override_node_options/override_node_options.install

@@ -5,9 +5,31 @@
  * Install, update and uninstall functions for the override_node_options module.
  */
 
+/**
+ * Implements hook_install().
+ */
+function override_node_options_install() {
+  db_update('system')->fields(array(
+    'weight' => -1
+  ))
+  ->condition('name', 'override_node_options', '=')
+  ->execute();
+}
+
 /**
  * Implements hook_uninstall().
  */
 function override_node_options_uninstall() {
   db_query("DELETE FROM {variable} WHERE name LIKE 'override_node_options_%'");
 }
+
+/**
+ * Implements hook_update_N().
+ */
+function override_node_options_update_7113() {
+  db_update('system')->fields(array(
+    'weight' => -1
+  ))
+  ->condition('name', 'override_node_options', '=')
+  ->execute();
+}

+ 14 - 4
sites/all/modules/contrib/admin/override_node_options/override_node_options.module

@@ -77,13 +77,23 @@ function override_node_options_form_alter(&$form, $form_state, $form_id) {
     // Add access to the 'Authoring information' fieldset.
     $form['author']['name']['#access'] = user_access('override ' . $node->type . ' authored by option');
     $form['author']['date']['#access'] = user_access('override ' . $node->type . ' authored on option');
-    $form['author']['#access'] |= element_get_visible_children($form['author']);
-
+    if (key_exists('#access', $form['author'])) {
+      $form['author']['#access'] |= element_get_visible_children($form['author']);
+    }
+    else {
+      $form['author']['#access'] = element_get_visible_children($form['author']);
+    }
+    
     // Add access to the 'Publishing options' fieldset.
     $form['options']['status']['#access'] = user_access('override ' . $node->type . ' published option');
     $form['options']['promote']['#access'] = user_access('override ' . $node->type . ' promote to front page option');
-    $form['options']['sticky']['#access'] = user_access('override ' . $node->type . ' sticky option');
-    $form['options']['#access'] |= element_get_visible_children($form['options']);
+    $form['options']['sticky']['#access'] = user_access('override ' . $node->type . ' sticky option');    
+    if (key_exists('#access', $form['options'])) {
+      $form['options']['#access'] |= element_get_visible_children($form['options']);
+    }
+    else {
+      $form['options']['#access'] = element_get_visible_children($form['options']);
+    }
 
     // @todo Remove when http://drupal.org/node/683630 is fixed.
     if ($form['author']['name']['#access']) {

+ 2 - 3
sites/all/modules/contrib/dev/devel/README.txt

@@ -22,9 +22,8 @@ Also a dpr() function is provided, which pretty prints arrays and strings.
 Useful during development. Many other nice functions like dpm(), dvm().
 
 AJAX developers in particular ought to install FirePHP Core from
-http://www.firephp.org/ and put it in the devel directory.
-This happens automatically when you enable via drush. You may also
-use a drush command to download the library. If downloading by hand,
+http://www.firephp.org/ and put it in the devel directory. You may
+use the devel-download drush command to download the library. If downloading by hand,
 your path to fb.php should look like devel/FirePHPCore/lib/FirePHPCore/fb.php.
 You can use svn checkout http://firephp.googlecode.com/svn/trunk/trunk/Libraries/FirePHPCore.
 Then you can log php variables to the Firebug console. Is quite useful.

+ 3 - 3
sites/all/modules/contrib/dev/devel/devel.admin.inc

@@ -60,7 +60,7 @@ function devel_admin_settings() {
   $form['xhprof']['settings']['devel_xhprof_directory'] = array(
     '#type' => 'textfield',
     '#title' => 'xhprof directory',
-    '#description' => t('Location of the xhprof source code on your system, usually somewhere in /usr/local/share or /usr/share, include the leading forward slash.'),
+    '#description' => t('Location of the xhprof source code on your system, where the directory "xhprof_lib" can be found, usually somewhere in /usr/local/share or /usr/share, include the leading forward slash.'),
     '#default_value' => variable_get('devel_xhprof_directory', ''),
     '#states' => array(
       'invisible' => array(
@@ -90,9 +90,9 @@ function devel_admin_settings() {
     '#description' => t('Display page execution time in the query log box.'),
   );
 
-  $form['dev_mem'] = array('#type' => 'checkbox',
+  $form['devel_memory'] = array('#type' => 'checkbox',
     '#title' => t('Display memory usage'),
-    '#default_value' => variable_get('dev_mem', 0),
+    '#default_value' => variable_get('devel_memory', 0),
     '#description' => t('Display how much memory is used to generate the current page. This will show memory usage when devel_init() is called and when devel_exit() is called.'),
   );
   $form['devel_redirect_page'] = array('#type' => 'checkbox',

+ 29 - 31
sites/all/modules/contrib/dev/devel/devel.drush.inc

@@ -12,21 +12,29 @@ function devel_drush_command() {
   $items['devel-download'] = array(
     'description' => dt('Downloads the FirePHP library from http://firephp.org/.'),
     'arguments' => array(
-      'path' => dt('Optional. A path to the download folder. If omitted Drush will use the default location (sites/all/libraries/firephp).'),
+      'path' => dt('Path to the download folder. This path is relative to the Drupal root. If omitted Drush will use the default location (sites/all/libraries/FirePHPCore).'),
     ),
   );
   $items['devel-reinstall'] = array(
     'description' => dt('Disable, Uninstall, and Install a list of projects.'),
+    'drush dependencies' => array('pm'),
     'arguments' => array(
       'projects' => dt('A space-separated list of project names.'),
     ),
+    'allow-additional-options' => array('pm-disable', 'pm-uninstall', 'pm-enable'),
+    'required-arguments' => 1,
     'aliases' => array('dre'),
   );
   $items['fn-hook'] = array(
-    'description' => 'List implementations of a given hook and explore source of specified one.',
+    'description' => 'List implementations of a given hook and explore the source of the selected one.',
     'arguments' => array(
-      'hook' => 'The name of the hook to explore.'
+      'hook' => 'The name of the hook to explore (e.g. "menu" for hook_menu()).'
     ),
+    'examples' => array(
+      'fn-hook cron' => 'List implementations of hook_cron().',
+    ),
+    'allow-additional-options' => array('fn-view'),
+    'required-arguments' => 1,
     'aliases' => array('fnh', 'hook'),
   );
   $items['fn-view'] = array(
@@ -44,11 +52,13 @@ function devel_drush_command() {
       'fn-view NodeController::load' => 'View the source code for method load in the class NodeController'
     ),
     'aliases' => array('fnv'),
+    'required-arguments' => 1,
   );
   $items['devel-token'] = array(
     'description' => dt('List available tokens'),
     'aliases' => array('token'),
     'core' => array(7), // Remove once 3.0 is released.
+    //@todo support --format option for json, csv, etc.
   );
   return $items;
 }
@@ -72,25 +82,28 @@ function drush_devel_reinstall() {
 /**
  * A command callback.
  */
-function drush_devel_download() {
-  $args = func_get_args();
-  if (isset($args[0])) {
-    $path = $args[0];
+function drush_devel_download($path = NULL) {
+  // If no path is provided by the user, set our default path.
+  if (is_null($path)) {
+    // We use devel folder for legacy reason.
+    $path = drupal_get_path('module', 'devel') . '/FirePHPCore';
   }
-  else {
-    $path = drush_get_context('DRUSH_DRUPAL_ROOT');
+  // If FirePHP is not installed and libraries module is enabled,
+  // try to find FirePHP by its own means.
+  if (!is_dir($path)) {
     if (module_exists('libraries')) {
-      $path .= '/' . libraries_get_path('FirePHPCore') . '/FirePHPCore';
-    }
-    else {
-      $path .= '/' . drupal_get_path('module', 'devel') . '/FirePHPCore';
+      // Libraries 1.x will return a path even if it doesn't exist
+      // while 2.x will return FALSE.
+      $path = libraries_get_path('FirePHPCore');
+      if (!$path) {
+        $path = 'sites/all/libraries/FirePHPCore';
+      }
     }
   }
-
   if (is_dir($path)) {
-    drush_log('FirePHP already present. No download required.', 'ok');
+    drush_log(dt('FirePHP already present at @path. No download required.', array('@path' => $path)), 'ok');
   }
-  elseif (drush_shell_exec('svn export http://firephp.googlecode.com/svn/branches/Library-FirePHPCore-0.3 ' . $path)) {
+  elseif (drush_shell_exec('svn export http://firephp.googlecode.com/svn/branches/Library-FirePHPCore-0.3 %s', $path)) {
     drush_log(dt('FirePHP has been exported via svn to @path.', array('@path' => $path)), 'success');
   }
   else {
@@ -98,21 +111,6 @@ function drush_devel_download() {
   }
 }
 
-/**
- * Implements drush_MODULE_post_COMMAND().
- */
-function drush_devel_post_pm_enable() {
-  $extensions = func_get_args();
-  // Deal with comma delimited extension list.
-  if (strpos($extensions[0], ',') !== FALSE) {
-    $extensions = explode(',', $extensions[0]);
-  }
-
-  if (in_array('devel', $extensions) && !drush_get_option('skip')) {
-    drush_devel_download();
-  }
-}
-
 /**
  * Command handler. Show hook implementations.
  */

+ 3 - 3
sites/all/modules/contrib/dev/devel/devel.info

@@ -7,9 +7,9 @@ tags[] = developer
 files[] = devel.test
 files[] = devel.mail.inc
 
-; Information added by drupal.org packaging script on 2012-06-05
-version = "7.x-1.3"
+; Information added by Drupal.org packaging script on 2014-05-01
+version = "7.x-1.5"
 core = "7.x"
 project = "devel"
-datestamp = "1338940281"
+datestamp = "1398963366"
 

+ 29 - 2
sites/all/modules/contrib/dev/devel/devel.install

@@ -31,18 +31,27 @@ function devel_enable() {
  * Implements hook_uninstall().
  */
 function devel_uninstall() {
-  variable_del('devel_form_weights');
   variable_del('devel_execution');
   variable_del('dev_timer');
   variable_del('devel_query_display');
   variable_del('devel_redirect_page');
   variable_del('devel_api_url');
-  variable_del('dev_mem');
+  variable_del('devel_memory');
   variable_del('devel_error_handlers');
   variable_del('devel_raw_names');
   variable_del('devel_switch_user_list_size');
   variable_del('devel_switch_user_include_anon');
   variable_del('devel_switch_user_show_form');
+  variable_del('devel_krumo_skin');
+  variable_del('devel_page_alter');
+  variable_del('devel_query_sort');
+  variable_del('devel_rebuild_theme_registry');
+  variable_del('devel_use_uncompressed_jquery');
+  variable_del('devel_xhprof_directory');
+  variable_del('devel_xhprof_enabled');
+  variable_del('devel_xhprof_url');
+  variable_del('devel_debug_mail_file_format');
+  variable_del('devel_debug_mail_directory');
 
   // Delete the development menu.
   if (module_exists('menu')) {
@@ -106,3 +115,21 @@ function devel_update_7004() {
     variable_set('devel_error_handlers', drupal_map_assoc($error_handlers));
   }
 }
+
+/**
+ * Delete variable 'devel_form_weights' from database as it was removed from code.
+ */
+function devel_update_7005() {
+  variable_del('devel_form_weights');
+}
+
+/**
+ * Change variable 'dev_mem' to 'devel_memory'.
+ */
+function devel_update_7006() {
+  if (variable_get('dev_mem', NULL) !== NULL) {
+    variable_set('devel_memory', variable_get('dev_mem'));
+  }
+
+  variable_del('dev_mem');
+}

+ 5 - 5
sites/all/modules/contrib/dev/devel/devel.js

@@ -2,11 +2,11 @@
 
 // Explain link in query log
 Drupal.behaviors.devel_explain = {
-  attach: function() {
+  attach: function(context, settings) {
     $('a.dev-explain').click(function () {
       qid = $(this).attr("qid");
       cell = $('#devel-query-' + qid);
-      $('.dev-explain', cell).load(Drupal.settings.basePath + '?q=devel/explain/' + Drupal.settings.devel.request_id + '/' + qid).show();
+      $('.dev-explain', cell).load(settings.basePath + '?q=devel/explain/' + settings.devel.request_id + '/' + qid).show();
       $('.dev-placeholders', cell).hide();
       $('.dev-arguments', cell).hide();
       return false;
@@ -16,11 +16,11 @@ Drupal.behaviors.devel_explain = {
 
 // Arguments link in query log
 Drupal.behaviors.devel_arguments = {
-  attach: function() {
+  attach: function(context, settings) {
     $('a.dev-arguments').click(function () {
       qid = $(this).attr("qid");
       cell = $('#devel-query-' + qid);
-      $('.dev-arguments', cell).load(Drupal.settings.basePath + '?q=devel/arguments/' + Drupal.settings.devel.request_id + '/' + qid).show();
+      $('.dev-arguments', cell).load(settings.basePath + '?q=devel/arguments/' + settings.devel.request_id + '/' + qid).show();
       $('.dev-placeholders', cell).hide();
       $('.dev-explain', cell).hide();
       return false;
@@ -30,7 +30,7 @@ Drupal.behaviors.devel_arguments = {
 
 // Placeholders link in query log
 Drupal.behaviors.devel_placeholders = {
-  attach: function() {
+  attach: function(context, settings) {
     $('a.dev-placeholders').click(function () {
       qid = $(this).attr("qid");
       cell = $('#devel-query-' + qid);

+ 4 - 1
sites/all/modules/contrib/dev/devel/devel.mail.inc

@@ -27,7 +27,10 @@ class DevelMailLog extends DefaultMailSystem {
 
     $line_endings = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
     $output = join($line_endings, $mimeheaders) . $line_endings;
-    $output .= $message['subject'] . $line_endings;
+    // 'Subject:' is a mail header and should not be translated.
+    $output .= 'Subject: ' . $message['subject'] . $line_endings;
+    // Blank line to separate headers from body.
+    $output .= $line_endings;
     $output .= preg_replace('@\r?\n@', $line_endings, $message['body']);
     return $output;
   }

File diff suppressed because it is too large
+ 8 - 6
sites/all/modules/contrib/dev/devel/devel.module


+ 8 - 11
sites/all/modules/contrib/dev/devel/devel.pages.inc

@@ -145,20 +145,17 @@ function devel_field_info_page() {
   $output .= kprint_r($info, TRUE, t('Instances'));
   $info = field_info_bundles();
   $output .= kprint_r($info, TRUE, t('Bundles'));
+  $info = field_info_field_types();
+  $output .= kprint_r($info, TRUE, t('Field types'));
+  $info = field_info_formatter_types();
+  $output .= kprint_r($info, TRUE, t('Formatter types'));
+  $info = field_info_storage_types();
+  $output .= kprint_r($info, TRUE, t('Storage types'));
+  $info = field_info_widget_types();
+  $output .= kprint_r($info, TRUE, t('Widget types'));
   return $output;
 }
 
-/**
- * Menu callback; display all variables.
- */
-function devel_variable_page() {
-  // We return our own $page so as to avoid blocks.
-  $output = drupal_get_form('devel_variable_form');
-  drupal_set_page_content($output);
-  $page = element_info('page');
-  return $page;
-}
-
 function devel_variable_form() {
   $header = array(
     'name' => array('data' => t('Name'), 'field' => 'name', 'sort' => 'asc'),

+ 2 - 1
sites/all/modules/contrib/dev/devel/devel.test

@@ -45,7 +45,8 @@ class DevelMailTest extends DrupalWebTestCase {
     $this->assertEqual($content, 'From: postmaster@example.com
 X-stupid: dumb
 To: drupal@example.com
-Test mail
+Subject: Test mail
+
 I am the body of this message');
   }
 

+ 47 - 11
sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.drush.inc

@@ -10,6 +10,7 @@
  */
 function devel_generate_drush_command() {
   $items['generate-users'] = array(
+    'callback' => 'drush_devel_generate_users',
     'description' => 'Create users.',
     'arguments' => array(
       'number_users' => 'Number of users to generate.',
@@ -17,10 +18,12 @@ function devel_generate_drush_command() {
     'options' => array(
       'kill' => 'Delete all users before generating new ones.',
       'roles' => 'A comma delimited list of role IDs which should be granted to the new users. No need to specify authenticated user role.',
+      'pass' => 'Specify a password to be set for all generated users.',
     ),
     'aliases' => array('genu'),
   );
   $items['generate-terms'] = array(
+    'callback' => 'drush_devel_generate_terms',
     'description' => 'Create terms in specified vocabulary.',
     'arguments' => array(
       'machine_name' => 'Vocabulary machine name into which new terms will be inserted.',
@@ -28,12 +31,13 @@ function devel_generate_drush_command() {
     ),
     'options' => array(
       'kill' => 'Delete all terms in specified vocabulary before generating.',
-      'feedback' => 'An integer representing interval for insertion rate logging. Defaults to 500',
+      'feedback' => 'An integer representing interval for insertion rate logging. Defaults to 1000',
     ),
     'aliases' => array('gent'),
 
   );
   $items['generate-vocabs'] = array(
+    'callback' => 'drush_devel_generate_vocabs',
     'description' => 'Create vocabularies.',
     'arguments' => array(
       'num_vocabs' => 'Number of vocabularies to create. Defaults to 1.',
@@ -44,6 +48,7 @@ function devel_generate_drush_command() {
     'aliases' => array('genv'),
   );
   $items['generate-content'] = array(
+    'callback' => 'drush_devel_generate_content',
     'description' => 'Create content.',
     'drupal dependencies' => array('devel_generate'),
     'arguments' => array(
@@ -53,13 +58,14 @@ function devel_generate_drush_command() {
     'options' => array(
       'kill' => 'Delete all content before generating new content.',
       'types' => 'A comma delimited list of content types to create. Defaults to page,article.',
-      'feedback' => 'An integer representing interval for insertion rate logging. Defaults to 500',
+      'feedback' => 'An integer representing interval for insertion rate logging. Defaults to 1000',
       'skip-fields' => 'A comma delimited list of fields to omit when generating random values',
       'languages' => 'A comma-separated list of language codes',
     ),
     'aliases' => array('genc'),
   );
   $items['generate-menus'] = array(
+    'callback' => 'drush_devel_generate_menus',
     'description' => 'Create menus and menu items.',
     'drupal dependencies' => array('devel_generate'), // Remove these once devel.module is moved down a directory. http://drupal.org/node/925246
     'arguments' => array(
@@ -86,7 +92,8 @@ function drush_devel_generate_users($num_users = NULL) {
   }
   drush_generate_include_devel();
   $roles = drush_get_option('roles') ? explode(',', drush_get_option('roles')) : array();
-  devel_create_users($num_users, drush_get_option('kill'), 0, $roles);
+  $pass = drush_get_option('pass', NULL);
+  devel_create_users($num_users, drush_get_option('kill'), 0, $roles, $pass);
   drush_log(t('Generated @number users.', array('@number' => $num_users)), 'success');
 }
 
@@ -103,9 +110,14 @@ function drush_devel_generate_terms($vname = NULL, $num_terms = 10) {
   }
 
   drush_generate_include_devel();
-  $vocabs[$vocab->vid] = $vocab;
-  devel_generate_term_data($vocabs, $num_terms, '12', drush_get_option('kill'));
-  drush_log(dt('Generated @num_terms terms.', array('@num_terms' => $num_terms)), 'success');
+  if (drush_get_option('kill')) {
+    devel_generate_delete_vocabulary_terms($vocab->vid);
+    drush_log(dt('Deleted existing terms.'), 'success');
+  }
+  $new_terms = devel_generate_terms($num_terms, array($vocab->vid => $vocab), '12');
+  if (!empty($new_terms)) {
+    drush_log(dt("Created the following new terms:\n!terms", array('!terms' => implode("\n", $new_terms))), 'success');
+  }
 }
 
 /**
@@ -116,8 +128,14 @@ function drush_devel_generate_vocabs($num_vocab = 1) {
     return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of vocabularies: !num.', array('!num' => $num_vocab)));
   }
   drush_generate_include_devel();
-  devel_generate_vocab_data($num_vocab, '12', drush_get_option('kill'));
-  drush_log(dt('Generated @num_vocab vocabularies.', array('@num_vocab' => $num_vocab)), 'success');
+  if (drush_get_option('kill')) {
+    devel_generate_delete_vocabularies();
+    drush_log(dt('Deleted existing vocabularies.'), 'success');
+  }
+  $new_vocs = devel_generate_vocabs($num_vocab, '12');
+  if (!empty($new_vocs)) {
+    drush_log(dt("Created the following new vocabularies:\n!vocs", array('!vocs' => implode("\n", $new_vocs))), 'success');
+  }
 }
 
 /**
@@ -152,6 +170,10 @@ function drush_devel_generate_content($num_nodes = NULL, $max_comments = NULL) {
   $values['values']['num_nodes'] = $num_nodes;
   $values['values']['max_comments'] = $max_comments;
   $values['values']['node_types'] = drupal_map_assoc(explode(',', drush_get_option('types', 'page,article')));
+  $node_types = array_filter($values['values']['node_types']);
+  if (!empty($values['values']['kill_content']) && empty($node_types)) {
+    return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide content type (--types) in which you want to delete the content.'));
+  }
   drush_generate_include_devel();
   devel_generate_content($values);
   drush_log(t('Generated @num_nodes nodes, @max_comments comments (or less) per node.', array('@num_nodes' => (int)$num_nodes, '@max_comments' => (int)$max_comments)), 'success');
@@ -179,12 +201,26 @@ function drush_devel_generate_menus($number_menus = 2, $number_links = 50, $max_
   $user = $user_one;
   drupal_save_session(FALSE);
 
-  $kill = drush_get_option('kill');
   drush_generate_include_devel();
+
+  // Delete custom menus.
+  if (drush_get_option('kill')) {
+    devel_generate_delete_menus();
+    drush_log(dt('Deleted existing menus and links.'), 'success');
+  }
+
+  // Generate new menus.
+  $new_menus = devel_generate_menus($number_menus, '12');
+  if (!empty($new_menus)) {
+    drush_log(dt("Created the following new menus:\n!menus", array('!menus' => implode("\n", $new_menus))), 'success');
+  }
+
+  // Generate new menu links.
   $link_types = drupal_map_assoc(array('node', 'front', 'external'));
-  devel_generate_menu_data($number_menus, array(), $number_links, 12, $link_types, $max_depth, $max_width, $kill);
-  drush_log(t('Generated @number_menus menus, @number_links links.', array('@number_menus' => (int)$number_menus, '@number_links' => (int)$number_links)), 'success');
+  $new_links = devel_generate_links($number_links, $new_menus, '12', $link_types, $max_depth, $max_width);
+  drush_log(dt('Created !count new menu links.', array('!count' => count($new_links))), 'success');
 }
+
 //////////////////////////////////////////////////////////////////////////////
 // Helper functions
 

+ 75 - 65
sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.inc

@@ -11,8 +11,10 @@
  *  The max age of each randomly-generated user, in seconds.
  * @param $roles
  *  An array of role IDs that the users should receive.
+ * @param $pass
+ *  A string to be used as common password for all generated users.
  */
-function devel_create_users($num, $kill, $age = 0, $roles = array()) {
+function devel_create_users($num, $kill, $age = 0, $roles = array(), $pass = NULL) {
   $url = parse_url($GLOBALS['base_url']);
   if ($kill) {
     $uids = db_select('users', 'u')
@@ -45,8 +47,8 @@ function devel_create_users($num, $kill, $age = 0, $roles = array()) {
       $edit = array(
         'uid'     => NULL,
         'name'    => $name,
-        'pass'    => NULL, // No password avoids user_hash_password() which is expensive.
-        'mail'    => $name . '@' . $url['host'],
+        'pass'    => $pass,
+        'mail'    => $name . '@' . $url['host'].'.invalid',
         'status'  => 1,
         'created' => REQUEST_TIME - mt_rand(0, $age),
         'roles' => drupal_map_assoc($roles),
@@ -103,6 +105,7 @@ function devel_create_users($num, $kill, $age = 0, $roles = array()) {
         // Save the user record with the new picture.
         $edit = (array) $account;
         $edit['picture'] = $file;
+        $edit['pass'] = $pass; // Reassign password as it is replaced with the hashed version in $account
         user_save($account, $edit);
       }
     }
@@ -114,8 +117,11 @@ function devel_create_users($num, $kill, $age = 0, $roles = array()) {
 /**
  * The main API function for creating content.
  *
- * See devel_generate_content_form() for the supported keys in $form_state['values'].
- * Other modules may participate by form_alter() on that form and then handling their data during hook_nodeapi('pre_save') or in own submit handler for the form.
+ * See devel_generate_content_form() for the supported keys in
+ * $form_state['values'].
+ * Other modules may participate by form_alter() on that form and then handling
+ * their data during hook_node_insert() or in their own submit handler for the
+ * form.
  *
  * @param string $form_state
  * @return void
@@ -205,6 +211,18 @@ function devel_generate_vocabs($records, $maxlength = 12, $types = array('page',
   return $vocs;
 }
 
+/**
+ * Generates taxonomy terms for a list of given vocabularies.
+ *
+ * @param $records
+ *   int number of terms to create in total.
+ * @param $vocabs
+ *   array list of vocabs to populate.
+ * @param $maxlength
+ *   int maximum length per term.
+ * @return
+ *   array the list of names of the created terms.
+ */
 function devel_generate_terms($records, $vocabs, $maxlength = 12) {
   $terms = array();
 
@@ -255,7 +273,7 @@ function devel_generate_terms($records, $vocabs, $maxlength = 12) {
 
     // Populate all core fields on behalf of field.module
     module_load_include('inc', 'devel_generate', 'devel_generate.fields');
-    devel_generate_fields($term, 'term', $term->vocabulary_machine_name);
+    devel_generate_fields($term, 'taxonomy_term', $term->vocabulary_machine_name);
 
     if ($status = taxonomy_term_save($term)) {
       $max += 1;
@@ -289,69 +307,49 @@ function devel_generate_get_terms($vids) {
            ->fetchCol('tid');
 }
 
-function devel_generate_term_data($vocabs, $num_terms, $title_length, $kill) {
-  if ($kill) {
-    foreach (devel_generate_get_terms(array_keys($vocabs)) as $tid) {
-      taxonomy_term_delete($tid);
-    }
-    drupal_set_message(t('Deleted existing terms.'));
-  }
-
-  $new_terms = devel_generate_terms($num_terms, $vocabs, $title_length);
-  if (!empty($new_terms)) {
-    drupal_set_message(t('Created the following new terms: !terms', array('!terms' => theme('item_list', array('items' => $new_terms)))));
+/**
+ * Deletes all terms of a vocabulary.
+ *
+ * @param $vid
+ *   int a vocabulary vid.
+ */
+function devel_generate_delete_vocabulary_terms($vid) {
+  foreach (taxonomy_get_tree($vid) as $term) {
+    taxonomy_term_delete($term->tid);
   }
 }
 
-function devel_generate_vocab_data($num_vocab, $title_length, $kill) {
-
-  if ($kill) {
-    foreach (taxonomy_get_vocabularies() as $vid => $vocab) {
-      taxonomy_vocabulary_delete($vid);
-    }
-    drupal_set_message(t('Deleted existing vocabularies.'));
-  }
-
-  $new_vocs = devel_generate_vocabs($num_vocab, $title_length);
-  if (!empty($new_vocs)) {
-    drupal_set_message(t('Created the following new vocabularies: !vocs', array('!vocs' => theme('item_list', array('items' => $new_vocs)))));
+/**
+ * Deletes all vocabularies.
+ */
+function devel_generate_delete_vocabularies() {
+  foreach (taxonomy_vocabulary_load_multiple(FALSE) as $vid => $vocab) {
+    taxonomy_vocabulary_delete($vid);
   }
 }
 
-function devel_generate_menu_data($num_menus, $existing_menus, $num_links, $title_length, $link_types, $max_depth, $max_width, $kill) {
-  // Delete menus and menu links.
-  if ($kill) {
-    if (module_exists('menu')) {
-      foreach (menu_get_menus(FALSE) as $menu => $menu_title) {
-        if (strpos($menu, 'devel-') === 0) {
-          $menu = menu_load($menu);
-          menu_delete($menu);
-        }
+/**
+ * Deletes custom generated menus
+ */
+function devel_generate_delete_menus() {
+  if (module_exists('menu')) {
+    foreach (menu_get_menus(FALSE) as $menu => $menu_title) {
+      if (strpos($menu, 'devel-') === 0) {
+        $menu = menu_load($menu);
+        menu_delete($menu);
       }
     }
-    // Delete menu links generated by devel.
-    $result = db_select('menu_links', 'm')
-      ->fields('m', array('mlid'))
-      ->condition('m.menu_name', 'devel', '<>')
-      // Look for the serialized version of 'devel' => TRUE.
-      ->condition('m.options', '%' . db_like('s:5:"devel";b:1') . '%', 'LIKE')
-      ->execute();
-    foreach ($result as $link) {
-      menu_link_delete($link->mlid);
-    }
-    drupal_set_message(t('Deleted existing menus and links.'));
   }
-
-  // Generate new menus.
-  $new_menus = devel_generate_menus($num_menus, $title_length);
-  if (!empty($new_menus)) {
-    drupal_set_message(t('Created the following new menus: !menus', array('!menus' => theme('item_list', array('items' => $new_menus)))));
+  // Delete menu links generated by devel.
+  $result = db_select('menu_links', 'm')
+    ->fields('m', array('mlid'))
+    ->condition('m.menu_name', 'devel', '<>')
+    // Look for the serialized version of 'devel' => TRUE.
+    ->condition('m.options', '%' . db_like('s:5:"devel";b:1') . '%', 'LIKE')
+    ->execute();
+  foreach ($result as $link) {
+    menu_link_delete($link->mlid);
   }
-
-  // Generate new menu links.
-  $menus = $new_menus + $existing_menus;
-  $new_links = devel_generate_links($num_links, $menus, $title_length, $link_types, $max_depth, $max_width);
-  drupal_set_message(t('Created @count new menu links.', array('@count' => count($new_links))));
 }
 
 /**
@@ -366,7 +364,7 @@ function devel_generate_menus($num_menus, $title_length = 12) {
 
   for ($i = 1; $i <= $num_menus; $i++) {
     $menu = array();
-    $menu['title'] = devel_generate_word(mt_rand(2, $title_length));
+    $menu['title'] = devel_generate_word(mt_rand(2, max(2, $title_length)));
     $menu['menu_name'] = 'devel-' . drupal_strtolower($menu['title']);
     $menu['description'] = t('Description of @name', array('@name' => $menu['title']));
     menu_save($menu);
@@ -394,7 +392,7 @@ function devel_generate_links($num_links, $menus, $title_length, $link_types, $m
       'options'     => array('devel' => TRUE),
       'weight'      => mt_rand(-50, 50),
       'mlid'        => 0,
-      'link_title'  => devel_generate_word(mt_rand(2, $title_length)),
+      'link_title'  => devel_generate_word(mt_rand(2, max(2, $title_length))),
     );
     $link['options']['attributes']['title'] = t('Description of @title.', array('@title' => $link['link_title']));
 
@@ -404,7 +402,7 @@ function devel_generate_links($num_links, $menus, $title_length, $link_types, $m
     }
     else {
       // Otherwise, get a random parent menu depth.
-      $depth = mt_rand(1, $max_depth - 1);
+      $depth = mt_rand(1, max(1, $max_depth - 1));
     }
     // Get a random parent link from the proper depth.
     do {
@@ -674,9 +672,19 @@ function devel_generate_content_add_node(&$results) {
   $users = $results['users'];
   $node->uid = $users[array_rand($users)];
   $type = node_type_get_type($node);
-  $node->title = $type->has_title ? devel_create_greeking(mt_rand(2, $results['title_length']), TRUE) : '';
   $node->revision = mt_rand(0,1);
   $node->promote = mt_rand(0, 1);
+
+  if ($type->has_title) {
+    // We should not use the random function if the value is not random
+    if ($results['title_length'] < 2) {
+      $node->title = devel_create_greeking(1, TRUE);
+    }
+    else {
+      $node->title = devel_create_greeking(mt_rand(1, $results['title_length']), TRUE);
+    }
+  }
+
   // Avoid NOTICE.
   if (!isset($results['time_range'])) {
     $results['time_range'] = 0;
@@ -686,14 +694,16 @@ function devel_generate_content_add_node(&$results) {
 
   $node->created = REQUEST_TIME - mt_rand(0, $results['time_range']);
 
-  // A flag to let hook_nodeapi() implementations know that this is a generated node.
+  // A flag to let hook_node_insert() implementations know that this is a
+  // generated node.
   $node->devel_generate = $results;
 
   // Populate all core fields on behalf of field.module
   module_load_include('inc', 'devel_generate', 'devel_generate.fields');
   devel_generate_fields($node, 'node', $node->type);
 
-  // See devel_generate_nodeapi() for actions that happen before and after this save.
+  // See devel_generate_node_insert() for actions that happen before and after
+  // this save.
   node_save($node);
 }
 

+ 4 - 4
sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info

@@ -2,13 +2,13 @@ name = Devel generate
 description = Generate dummy users, nodes, and taxonomy terms.
 package = Development
 core = 7.x
-dependencies[] = devel
 tags[] = developer
 configure = admin/config/development/generate
+files[] = devel_generate.test
 
-; Information added by drupal.org packaging script on 2012-06-05
-version = "7.x-1.3"
+; Information added by Drupal.org packaging script on 2014-05-01
+version = "7.x-1.5"
 core = "7.x"
 project = "devel"
-datestamp = "1338940281"
+datestamp = "1398963366"
 

+ 76 - 17
sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module

@@ -71,6 +71,14 @@ function devel_generate_users_form() {
     '#options' => $options,
   );
 
+  $form['pass'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Password to be set'),
+    '#default_value' => NULL,
+    '#size' => 32,
+    '#description' => t('Leave this field empty if you do not need to set a password.'),
+  );
+
   $options = array(1 => t('Now'));
   foreach (array(3600, 86400, 604800, 2592000, 31536000) as $interval) {
     $options[$interval] = format_interval($interval, 1) . ' ' . t('ago');
@@ -93,10 +101,10 @@ function devel_generate_users_form() {
 /**
  * FormAPI submission to generate users.
  */
-function devel_generate_users_form_submit($form_id, &$form_state) {
+function devel_generate_users_form_submit($form, &$form_state) {
   module_load_include('inc', 'devel_generate');
   $values = $form_state['values'];
-  devel_create_users($values['num'], $values['kill_users'], $values['time_range'], $values['roles']);
+  devel_create_users($values['num'], $values['kill_users'], $values['time_range'], $values['roles'], $values['pass']);
 }
 
 /**
@@ -117,14 +125,15 @@ function devel_generate_content_form() {
   }
   else {
     $types = node_type_get_types();
-    $suffix = '';
     foreach ($types as $type) {
+      $options[$type->type] = array(
+        'type' => t($type->name),
+      );
       if (module_exists('comment')) {
         $default = variable_get('comment_' . $type->type, COMMENT_NODE_OPEN);
         $map = array(t('Hidden'), t('Closed'), t('Open'));
-        $suffix = '<small>. ' . t('Comments: ') . $map[$default]. '</small>';
+        $options[$type->type]['comments'] = '<small>'. $map[$default]. '</small>';
       }
-      $options[$type->type] = t($type->name) . $suffix;
     }
   }
   // we cannot currently generate valid polls.
@@ -135,11 +144,18 @@ function devel_generate_content_form() {
     return;
   }
 
+  $header = array(
+    'type' => t('Content type'),
+  );
+  if (module_exists('comment')) {
+    $header['comments'] = t('Comments');
+  }
+
   $form['node_types'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Content types'),
+    '#type' => 'tableselect',
+    '#header' => $header,
     '#options' => $options,
-    '#default_value' => array_keys($options),
+    '#required' => TRUE,
   );
   if (module_exists('checkall')) $form['node_types']['#checkall'] = TRUE;
   $form['kill_content'] = array(
@@ -218,12 +234,21 @@ function devel_generate_content_form() {
   return $form;
 }
 
+/**
+ * FormAPI validation before generate nodes.
+ */
+function devel_generate_content_form_validate($form, &$form_state) {
+  form_set_value($form['node_types'], array_filter($form_state['values']['node_types']) , $form_state);
+  if (!empty($form_state['values']['kill_content']) && empty($form_state['values']['node_types'])) {
+    form_set_error('', t('Please select at least one content type to delete the content.'));
+  }
+}
+
 /**
  * FormAPI submission to generate nodes.
  */
-function devel_generate_content_form_submit($form_id, &$form_state) {
+function devel_generate_content_form_submit($form, &$form_state) {
   module_load_include('inc', 'devel_generate', 'devel_generate');
-  $form_state['values']['node_types'] = array_filter($form_state['values']['node_types']);
   if ($form_state['values']['num_nodes'] <= 50 && $form_state['values']['max_comments'] <= 10) {
     module_load_include('inc', 'devel_generate');
     devel_generate_content($form_state);
@@ -305,18 +330,36 @@ function devel_generate_vocab_form() {
 /**
  * FormAPI submission to generate taxonomy terms.
  */
-function devel_generate_term_form_submit($form_id, &$form_state) {
+function devel_generate_term_form_submit($form, &$form_state) {
+  $values = $form_state['values'];
   module_load_include('inc', 'devel_generate');
-  $vocabs = taxonomy_vocabulary_load_multiple($form_state['values']['vids']);
-  devel_generate_term_data($vocabs, $form_state['values']['num_terms'], $form_state['values']['title_length'], $form_state['values']['kill_taxonomy']);
+  if ($values['kill_taxonomy']) {
+    foreach ($values['vids'] as $vid) {
+      devel_generate_delete_vocabulary_terms($vid);
+    }
+    drupal_set_message(t('Deleted existing terms.'));
+  }
+  $vocabs = taxonomy_vocabulary_load_multiple($values['vids']);
+  $new_terms = devel_generate_terms($values['num_terms'], $vocabs, $values['title_length']);
+  if (!empty($new_terms)) {
+    drupal_set_message(t('Created the following new terms: !terms', array('!terms' => implode(', ', $new_terms))));
+  }
 }
 
 /**
  * FormAPI submission to generate taxonomy vocabularies.
  */
-function devel_generate_vocab_form_submit($form_id, &$form_state) {
+function devel_generate_vocab_form_submit($form, &$form_state) {
+  $values = $form_state['values'];
   module_load_include('inc', 'devel_generate');
-  devel_generate_vocab_data($form_state['values']['num_vocabs'], $form_state['values']['title_length'], $form_state['values']['kill_taxonomy']);
+  if ($values['kill_taxonomy']) {
+    devel_generate_delete_vocabularies();
+    drupal_set_message(t('Deleted existing vocabularies.'));
+  }
+  $new_vocs = devel_generate_vocabs($values['num_vocabs'], $values['title_length']);
+  if (!empty($new_vocs)) {
+    drupal_set_message(t('Created the following new vocabularies: !vocs', array('!vocs' => implode(', ', $new_vocs))));
+  }
 }
 
 /**
@@ -407,6 +450,7 @@ function devel_generate_menu_form() {
   $form['title_length'] = array(
     '#type' => 'textfield',
     '#title' => t('Maximum number of characters in menu and menu link names'),
+    '#description' => t("The minimum length is 2."),
     '#default_value' => 12,
     '#size' => 10,
     '#required' => TRUE,
@@ -453,11 +497,26 @@ function devel_generate_menu_form() {
 /**
  * FormAPI submission to generate menus.
  */
-function devel_generate_menu_form_submit($form_id, &$form_state) {
+function devel_generate_menu_form_submit($form, &$form_state) {
   // If the create new menus checkbox is off, set the number of new menus to 0.
   if (!isset($form_state['values']['existing_menus']['__new-menu__']) || !$form_state['values']['existing_menus']['__new-menu__']) {
     $form_state['values']['num_menus'] = 0;
   }
   module_load_include('inc', 'devel_generate');
-  devel_generate_menu_data($form_state['values']['num_menus'], $form_state['values']['existing_menus'], $form_state['values']['num_links'], $form_state['values']['title_length'], $form_state['values']['link_types'], $form_state['values']['max_depth'], $form_state['values']['max_width'], $form_state['values']['kill']);
+  // Delete custom menus.
+  if ($form_state['values']['kill']) {
+    devel_generate_delete_menus();
+    drupal_set_message(t('Deleted existing menus and links.'));
+  }
+
+  // Generate new menus.
+  $new_menus = devel_generate_menus($form_state['values']['num_menus'], $form_state['values']['title_length']);
+  if (!empty($new_menus)) {
+    drupal_set_message(t('Created the following new menus: !menus', array('!menus' => implode(', ', $new_menus))));
+  }
+
+  // Generate new menu links.
+  $menus = $new_menus + $form_state['values']['existing_menus'];
+  $new_links = devel_generate_links($form_state['values']['num_links'], $menus, $form_state['values']['title_length'], $form_state['values']['link_types'], $form_state['values']['max_depth'], $form_state['values']['max_width']);
+  drupal_set_message(t('Created @count new menu links.', array('@count' => count($new_links))));
 }

+ 108 - 0
sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test

@@ -0,0 +1,108 @@
+<?php
+/**
+ * @file
+ * Implements tests for devel_generate submodule.
+ */
+
+/**
+ * class DevelGenerateTest
+ */
+class DevelGenerateTest extends DrupalWebTestCase {
+  /*
+   * The getInfo() method provides information about the test.
+   * In order for the test to be run, the getInfo() method needs
+   * to be implemented.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Devel Generate'),
+      'description' => t('Tests the logic to generate data.'),
+      'group' => t('Devel'),
+    );
+  }
+
+  /**
+   * Prepares the testing environment
+   */
+  function setUp() {
+    parent::setUp(array('devel', 'devel_generate', 'taxonomy', 'menu', 'comment'));
+
+    // Create Basic page and Article node types.
+    if ($this->profile != 'standard') {
+      $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic Page'));
+    }
+  }
+
+  /**
+   * Tests generate commands
+   */
+  public function testGenerate() {
+    $user = $this->drupalCreateUser(array(
+      'administer taxonomy',
+      'administer menu',
+      'administer nodes',
+    ));
+    $this->drupalLogin($user);
+
+    // Generate taxonomy vocabularies.
+    $edit = array(
+      'num_vocabs' => 5,
+      'title_length' => 12,
+      'kill_taxonomy' => 1,
+    );
+    $this->drupalPost('admin/config/development/generate/vocabs',
+                      $edit, t('Generate'));
+    $this->assertText(t('Deleted existing vocabularies.'));
+    $this->assertText(t('Created the following new vocabularies:'));
+
+    // Generate taxonomy terms.
+    $form = devel_generate_term_form();
+    $vids = array_keys($form['vids']['#options']);
+    $edit = array(
+      'vids[]' => $vids,
+      'num_terms' => 5,
+      'title_length' => 12,
+      'kill_taxonomy' => 1,
+    );
+    $this->drupalPost('admin/config/development/generate/taxonomy',
+                      $edit, t('Generate'));
+    $this->assertText(t('Deleted existing terms.'));
+    $this->assertText(t('Created the following new terms: '));
+
+    // Generate menus.
+    $edit = array(
+      'existing_menus[__new-menu__]' => 1,
+      'num_menus' => 2,
+      'num_links' => 50,
+      'title_length' => 12,
+      'link_types[node]' => 1,
+      'link_types[front]' => 1,
+      'link_types[external]' => 1,
+      'max_depth' => 4,
+      'max_width' => 6,
+      'kill' => 1,
+    );
+    $this->drupalPost('admin/config/development/generate/menu',
+                      $edit, t('Generate'));
+    $this->assertText(t('Deleted existing menus and links.'));
+    $this->assertText(t('Created the following new menus:'));
+    $this->assertText(t('Created 50 new menu links.'));
+
+    // Generate content.
+    // First we create a node in order to test the Delete content checkbox.
+    $this->drupalCreateNode(array());
+
+    // Now submit the generate content form.
+    $edit = array(
+      'node_types[page]' => 1,
+      'kill_content' => 1,
+      'num_nodes' => 2,
+      'time_range' => 604800,
+      'max_comments' => 3,
+      'title_length' => 4,
+    );
+    $this->drupalPost('admin/config/development/generate/content', $edit, t('Generate'));
+    $this->assertText(t('Deleted 1 nodes.'));
+    $this->assertText(t('Finished creating 2 nodes'));
+  }
+}

+ 1 - 1
sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc

@@ -18,7 +18,7 @@ function _file_devel_generate($object, $field, $instance, $bundle) {
       $source->uri = $path;
       $source->uid = 1; // TODO: randomize? use case specific.
       $source->filemime = 'text/plain';
-      $source->filename = array_pop(explode("//", $path));
+      $source->filename = basename($path);
       $destination_dir = $field['settings']['uri_scheme'] . '://' . $instance['settings']['file_directory'];
       file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
       $destination = $destination_dir . '/' . basename($path);

+ 2 - 4
sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc

@@ -23,7 +23,7 @@ function _image_devel_generate($object, $field, $instance, $bundle) {
 
   $min_resolution = empty($instance['settings']['min_resolution']) ? '100x100' : $instance['settings']['min_resolution'];
   $max_resolution = empty($instance['settings']['max_resolution']) ? '600x600' : $instance['settings']['max_resolution'];
-  $extensions = array_intersect(explode(' ', $instance['settings']['file_extensions']), array('png', 'jpg'));
+  $extensions = array_intersect(explode(' ', $instance['settings']['file_extensions']), array('png', 'gif', 'jpg', 'jpeg'));
   $extension = array_rand(drupal_map_assoc($extensions));
 
   // Generate a max of 5 different images.
@@ -33,7 +33,7 @@ function _image_devel_generate($object, $field, $instance, $bundle) {
       $source->uri = $path;
       $source->uid = 1; // TODO: randomize? Use case specific.
       $source->filemime = 'image/' . pathinfo($path, PATHINFO_EXTENSION);
-      $source->filename = array_pop(explode("//", $path));
+      $source->filename = basename($path);
       $destination_dir = $field['settings']['uri_scheme'] . '://' . $instance['settings']['file_directory'];
       file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
       $destination = $destination_dir . '/' . basename($path);
@@ -89,8 +89,6 @@ function devel_generate_image($extension = 'png', $min_resolution, $max_resoluti
 
     $save_function = 'image'. ($extension == 'jpg' ? 'jpeg' : $extension);
     $save_function($im, drupal_realpath($destination));
-
-    $images[$extension][$min_resolution][$max_resolution][$destination] = $destination;
   }
   return $destination;
 }

+ 2 - 6
sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc

@@ -19,7 +19,7 @@ function _text_devel_generate($object, $field, $instance, $bundle) {
     $format = filter_fallback_format();
   }
 
-  if ($instance['widget']['type'] != 'text_textfield') {
+  if (empty($field['settings']['max_length'])) {
     // Textarea handling
     $object_field['value'] = devel_create_content($format);
     if ($instance['widget']['type'] == 'text_textarea_with_summary' && !empty($instance['display_summary'])) {
@@ -28,11 +28,7 @@ function _text_devel_generate($object, $field, $instance, $bundle) {
   }
   else {
     // Textfield handling.
-    // Generate a value that respects max_length.
-    if (empty($field['settings']['max_length'])) {
-      $field['settings']['max_length'] = 12;
-    }
-    $object_field['value'] = user_password($field['settings']['max_length']);
+    $object_field['value'] = substr(devel_create_greeking(mt_rand(1, $field['settings']['max_length'] / 6), FALSE), 0, $field['settings']['max_length']);
   }
   $object_field['format'] = $format;
   return $object_field;

+ 1 - 1
sites/all/modules/contrib/dev/devel/devel_krumo_path.js

@@ -7,7 +7,7 @@ Drupal.behaviors.devel = {
   attach: function (context, settings) {
 
     // Add hint to footnote
-    $('.krumo-footnote .krumo-call').before('<img style="vertical-align: middle;" title="Click to expand. Double-click to show path." src="' + Drupal.settings.basePath + 'misc/help.png"/>');
+    $('.krumo-footnote .krumo-call').once().before('<img style="vertical-align: middle;" title="Click to expand. Double-click to show path." src="' + settings.basePath + 'misc/help.png"/>');
 
     var krumo_name = [];
     var krumo_type = [];

+ 3 - 3
sites/all/modules/contrib/dev/devel/devel_node_access.info

@@ -6,9 +6,9 @@ core = 7.x
 configure = admin/config/development/devel
 tags[] = developer
 
-; Information added by drupal.org packaging script on 2012-06-05
-version = "7.x-1.3"
+; Information added by Drupal.org packaging script on 2014-05-01
+version = "7.x-1.5"
 core = "7.x"
 project = "devel"
-datestamp = "1338940281"
+datestamp = "1398963366"
 

+ 7 - 7
sites/all/modules/contrib/dev/devel/devel_node_access.js

@@ -7,7 +7,7 @@
   /**
    * Perform the access by user ajax request.
    */
-  function devel_node_access_user_ajax(context) {
+  function devel_node_access_user_ajax(context, settings) {
     // Get the cell ID for the first .dna-permission that isn't processed.
     var cell = $('td.dna-permission', context)
                .not('.ajax-processed', context)
@@ -15,7 +15,7 @@
     if (cell !== undefined) {
       // Generate the URI from the basePath, path, data type, cell ID, and a
       // random token to bypass caching.
-      var url = Drupal.settings.basePath
+      var url = settings.basePath
               + "?q="
               + 'devel/node_access/by_user/json/'
               + cell
@@ -25,7 +25,7 @@
       $.getJSON(url, function(data) {
         $('#' + cell, context).html(data).addClass('ajax-processed');
         // Call this function again.
-        devel_node_access_user_ajax(context);
+        devel_node_access_user_ajax(context, settings);
       });
       // Ajax fails silently on error, mark bad requests with an error message.
       // If the request is just slow this will update when the request succeeds.
@@ -42,7 +42,7 @@
               )
               .addClass('ajax-processed');
             // Call this function again.
-            devel_node_access_user_ajax(context);
+            devel_node_access_user_ajax(context, settings);
           }
         },
         3000
@@ -55,10 +55,10 @@
    * Attach the access by user behavior which initiates ajax.
    */
   Drupal.behaviors.develNodeAccessUserAjax = {
-    attach: function(context) {
+    attach: function(context, settings) {
       // Start the ajax.
-      devel_node_access_user_ajax(context);
+      devel_node_access_user_ajax(context, settings);
     }
   };
 
-})(jQuery);
+})(jQuery);

+ 48 - 11
sites/all/modules/contrib/dev/devel/devel_node_access.module

@@ -226,9 +226,13 @@ function dna_visible_nodes($nid = NULL) {
   if ($nid) {
     $nids[$nid] = $nid;
   }
-  elseif (empty($nids) && arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == NULL) {
-    // show DNA information on node/NID even if access is denied (IF the user has the 'view devel_node_access information' permission)!
-    return array(arg(1));
+  elseif (empty($nids)) {
+    $menu_item = menu_get_item();
+    $map = $menu_item['original_map'];
+    if ($map[0] == 'node' && isset($map[1]) && is_numeric($map[1]) && !isset($map[2])) {
+      // show DNA information on node/NID even if access is denied (IF the user has the 'view devel_node_access information' permission)!
+      return array($map[1]);
+    }
   }
   return $nids;
 }
@@ -368,6 +372,7 @@ function _devel_node_access_nar_alter(&$grants, $node) {
       }
       else {
         // it's an existing grant, check for changes
+        $view = $update = $delete = FALSE;
         foreach (array('view', 'update', 'delete') as $op) {
           $$op = $grant["grant_$op"] - $data[$grant['realm']][$grant['gid']]['current']["grant_$op"];
         }
@@ -496,6 +501,7 @@ function devel_node_access_block_info() {
   $blocks['dna_user'] = array(
     'info'   => t('Devel Node Access by User'),
     'region' => 'footer',
+    'status' => 0,
     'cache'  => DRUPAL_NO_CACHE,
   );
   return $blocks;
@@ -645,7 +651,7 @@ function devel_node_access_block_view($delta) {
               foreach (array('view', 'update', 'delete') as $op) {
                 $grants = _devel_node_access_module_invoke_all('node_grants', $user, $op);
                 // call all hook_node_grants_alter() implementations
-                $ng_alter_data = _devel_node_access_ng_alter($grants, $user, $op);
+                $ng_alter_datas[$op] = _devel_node_access_ng_alter($grants, $user, $op);
                 $checked_grants[$nid][$op] = array_merge(array('all' => array(0)), $grants);
               }
             }
@@ -788,7 +794,15 @@ function devel_node_access_block_view($delta) {
             'data'  => '<a href="#node-' . $grant['nid'] . '">' . $row['nid'] . '</a>',
             'title' => $grant['#title'],
           );
-          $row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':<br />') . $grant['realm'];
+          if (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0) {
+            $row['realm'] = $grant['realm'];
+          }
+          else {
+            $row['realm'] = array(
+              'data' => $grant['#module'] . ':<br />' . $grant['realm'],
+              'title' => t("The '@module' module fails to adhere to the best practice of naming its realm(s) after itself.", array('@module' => $grant['#module'])),
+            );
+          }
 
           // prepend information from the D7 hook_node_access_records_alter()
           $next_style = array();
@@ -906,7 +920,7 @@ function devel_node_access_block_view($delta) {
           );
         }
         else {
-          $variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $ng_alter_data) . '</div>';
+          $variables['!list'] = '<div style="margin-left: 2em">' . _devel_node_access_get_grant_list($nid, $ng_alter_datas['view']) . '</div>';
           $variables['%access'] = 'view';
           $output[] = array(
             '#prefix' => "\n<div style='text-align: left' title='" . t('These are the grants returned by hook_node_grants() for this user.') . "'>",
@@ -915,7 +929,10 @@ function devel_node_access_block_view($delta) {
           );
           $accounts[] = $user;
         }
-        if (arg(0) == 'node' && is_numeric(arg(1)) && !$block1_visible) {  // only for single nodes
+        $menu_item = menu_get_item();
+        $map = $menu_item['original_map'];
+        if ($map[0] == 'node' && isset($map[1]) && is_numeric($map[1]) && !isset($map[2]) && !$block1_visible) {
+          // only for single nodes
           if (user_is_logged_in()) {
             $accounts[] = user_load(0);  // Anonymous, too
           }
@@ -948,10 +965,11 @@ function devel_node_access_block_view($delta) {
                 '#suffix' => '</div>',
               );
             }
-            $variables['!username'] = theme('username', array('account' => $account));
+            $variables['!username'] = '<em class="placeholder">' . theme('username', array('account' => $account)) . '</em>';
             $output[] = array(
               '#prefix' => "\n<div style='text-align: left'>",
-              '#markup' => t("!username has the following access", $variables),
+              '#type'   => 'item',
+              'lead-in' => array('#markup' => t("!username has the following access", $variables) . ' '),
               'items'   => $account_items,
               '#suffix' => "\n</div>\n",
             );
@@ -971,9 +989,28 @@ function devel_node_access_block_view($delta) {
 
     case 'dna_user':
       // show which users can access this node
-      if (arg(0) == 'node' && is_numeric($nid = arg(1)) && arg(2) == NULL && $node = node_load($nid)) {
+      $menu_item = menu_get_item();
+      $map = $menu_item['original_map'];
+      if ($map[0] != 'node' || !isset($map[1]) || !is_numeric($map[1]) || isset($map[2])) {
+        // Ignore anything but node/%.
+        return;
+      }
+
+      if (isset($menu_item['map'][1]) && is_object($node = $menu_item['map'][1])) {
+        // We have the node.
+      }
+      elseif (is_numeric($menu_item['original_map'][1])) {
+        $node = node_load($menu_item['original_map'][1]);
+      }
+      if (isset($node)) {
+        $nid = $node->nid;
         $node_type = node_type_get_type($node);
-        $headers = array(t('username'), '<span title="' . t("Create nodes of the '@Node_type' type.", array('@Node_type' => $node_type->name)) . '">' . t('create') . '</span>', t('view'), t('update'), t('delete'));
+        $variables = array('@Node_type' => ($node_type ? $node_type->name : $node->type));
+        $create_header = '<span title="' . t("Create nodes of the '@Node_type' type.", $variables) . '">' . t('create') . '</span>';
+        if (!$node_type) {
+          $create_header .= '<br /><span class="error">' . t("(missing type: '@Node_type')", $variables) . '</span>';
+        }
+        $headers = array(t('username'), $create_header, t('view'), t('update'), t('delete'));
         $rows = array();
         // Determine whether to use Ajax or prepopulate the tables.
         if ($ajax = variable_get('devel_node_access_user_ajax', FALSE)) {

+ 2 - 1
sites/all/modules/contrib/dev/devel/krumo/class.krumo.php

@@ -604,7 +604,8 @@ This is a list of all the values from the <code><b><?php echo realpath($ini_file
     $_recursion_marker = krumo::_marker();
     if ($hive =& krumo::_hive($dummy)) {
       foreach($hive as $i=>$bee){
-        if (is_object($bee)) {
+        // skip closures set as properties
+        if (is_object($bee) && !($bee instanceof Closure)) {
           unset($hive[$i]->$_recursion_marker);
           // DEVEL: changed 'else' to 'elseif' below
           } elseif (is_array($bee)) {

+ 22 - 0
sites/all/modules/contrib/dev/devel/runtests.sh

@@ -0,0 +1,22 @@
+#!/usr/bin/env sh
+
+# This script will run phpunit-based test classes using Drush's
+# test framework.  First, the Drush executable is located, and
+# then phpunit is invoked, passing in drush_testcase.inc as
+# the bootstrap file.
+#
+# Any parameters that may be passed to phpunit may also be used
+# with runtests.sh.
+
+DRUSH_PATH="`which drush`"
+DRUSH_DIRNAME="`dirname -- "$DRUSH_PATH"`"
+
+# if [ $# = 0 ] ; then
+#   phpunit --bootstrap="$DRUSH_DIRNAME/tests/drush_testcase.inc" .
+# else
+#   phpunit --bootstrap="$DRUSH_DIRNAME/tests/drush_testcase.inc" "$@"
+# fi
+
+#Instead, hard code target file so we don't find a simpletest file at
+# /lib/Drupal/devel_generate/Tests/DevelGenerateTest.php.
+phpunit --bootstrap="$DRUSH_DIRNAME/tests/drush_testcase.inc" develDrushTest.php

+ 22 - 6
sites/all/modules/contrib/dev/libraries/CHANGELOG.txt

@@ -1,4 +1,22 @@
 
+Libraries 7.x-2.2, 2014-02-09
+-----------------------------
+#2046919 by tstoeckler: Clarify 'version' docs.
+#1946110 by munroe_richard: Allow uppercase letters as library machine names.
+#1953260 by tstoeckler: Improve documentation of libraries_get_version().
+#1855918 by tstoeckler: Make integration file loading backwards-compatible.
+#1876124 by tstoeckler: Fix integration files for themes.
+#1876124 by tstoeckler: Add tests for theme-provided library information.
+#1876124 by tstoeckler: Prepare for adding a test theme.
+#1876124 by tstoeckler | whastings, fubhy: Fix hook_libraries_info() for themes.
+#2015721 by tstoeckler, CaptainHook: Protect against files overriding local variables.
+#2046919 by tstoeckler: Improve documentation around 'version callback'.
+#1844272 by tstoeckler, jweowu: Fix typos in libraries.api.php.
+#1938638 by tstoeckler: Prevent weird PHP notice on update.
+#1329388 by RobLoach, tstoeckler: Clear static caches in libraries_flush_caches().
+#1855918 by rbayliss: Load integration files after library files.
+#1938638 by Pol: Fix typo in libraries.api.php.
+
 Libraries 7.x-2.1, 2013-03-09
 -----------------------------
 #1937446 by Pol, tstoeckler: Add a 'pre-dependencies-load' callback group.
@@ -13,7 +31,6 @@ Libraries 7.x-2.0, 2012-07-29
 #1578618 by iamEAP: Fixed Fatal cache flush failure on major version upgrades.
 #1449346 by tstoeckler, sun: Clean-up libraries.test
 
-
 Libraries 7.x-2.0-alpha2, 2011-12-15
 ------------------------------------
 #1299076 by tstoeckler: Improve testing of JS, CSS, and PHP files.
@@ -23,7 +40,6 @@ Libraries 7.x-2.0-alpha2, 2011-12-15
 #1321372 by Rob Loach: Provide a 'post-load' callback group.
 #1205854 by tstoeckler, sun: Test library caching.
 
-
 Libraries 7.x-2.0-alpha1, 2011-10-01
 ------------------------------------
 #1268342 by tstoeckler: Clean up drush libraries-list command.
@@ -65,20 +81,20 @@ by sun: Fixed testbot breaks upon .info file without .module file.
 Libraries 7.x-1.x, xxxx-xx-xx
 -----------------------------
 
-
 Libraries 7.x-1.0, 2010-01-27
 -----------------------------
 #743522 by sun: Ported to D7.
 
 
+Libraries 6.x-1.x, xxxx-xx-xx
+-----------------------------
+
 Libraries 6.x-1.0, 2010-01-27
 -----------------------------
 #1028744 by tstoeckler: Code clean-up.
 #496732 by tstoeckler, robphillips: Allow placing libraries in root directory.
 
-
-Libraries 6.x-1.0-ALPHA1, 2009-12-30
+Libraries 6.x-1.0-alpha1, 2009-12-30
 ------------------------------------
 #480440 by markus_petrux: Fixed base_path() not applied to default library path.
 #320562 by sun: Added basic functions.
-

+ 42 - 18
sites/all/modules/contrib/dev/libraries/libraries.api.php

@@ -30,7 +30,9 @@
  *     changes of implementing modules and to support different versions of a
  *     library simultaneously (though only one version can be installed per
  *     site). A valid use-case is an external library whose version cannot be
- *     determined programatically.
+ *     determined programmatically. Either 'version' or 'version callback' (or
+ *     'version arguments' in case libraries_get_version() is being used as a
+ *     version callback) must be declared.
  *   - version callback: (optional) The name of a function that detects and
  *     returns the full version string of the library. The first argument is
  *     always $library, an array containing all library information as described
@@ -38,18 +40,22 @@
  *     arguments, either as a single $options parameter or as multiple
  *     parameters, which correspond to the two ways to specify the argument
  *     values (see 'version arguments'). Defaults to libraries_get_version().
- *   - version arguments: A list of arguments to pass to the version callback.
- *     Version arguments can be declared either as an associative array whose
- *     keys are the argument names or as an indexed array without specifying
- *     keys. If declared as an associative array, the arguments get passed to
- *     the version callback as a single $options parameter whose keys are the
- *     argument names (i.e. $options is identical to the specified array). If
- *     declared as an indexed array, the array values get passed to the version
- *     callback as seperate arguments in the order they were declared. The
- *     default version callback libraries_get_version() expects a single,
- *     associative array with named keys:
- *     - file: The filename to parse for the version, relative to the library
- *       path. For example: 'docs/changelog.txt'.
+ *     Unless 'version' is declared or libraries_get_version() is being used as
+ *     a version callback, 'version callback' must be declared. In the latter
+ *     case, however, 'version arguments' must be declared in the specified way.
+ *   - version arguments: (optional) A list of arguments to pass to the version
+ *     callback. Version arguments can be declared either as an associative
+ *     array whose keys are the argument names or as an indexed array without
+ *     specifying keys. If declared as an associative array, the arguments get
+ *     passed to the version callback as a single $options parameter whose keys
+ *     are the argument names (i.e. $options is identical to the specified
+ *     array). If declared as an indexed array, the array values get passed to
+ *     the version callback as separate arguments in the order they were
+ *     declared. The default version callback libraries_get_version() expects a
+ *     single, associative array with named keys:
+ *     - file: The filename to parse for the version, relative to the path
+ *       speficied as the 'library path' property (see above). For example:
+ *       'docs/changelog.txt'.
  *     - pattern: A string containing a regular expression (PCRE) to match the
  *       library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note
  *       that the returned version is not the match of the entire pattern (i.e.
@@ -60,6 +66,10 @@
  *     - cols: (optional) The maximum number of characters per line to take into
  *       account. Defaults to 200. In case of minified or compressed files, this
  *       prevents reading the entire file into memory.
+ *     Defaults to an empty array. 'version arguments' must be specified unless
+ *     'version' is declared or the specified 'version callback' does not
+ *     require any arguments. The latter might be the case with a
+ *     library-specific version callback, for example.
  *   - files: An associative array of library files to load. Supported keys are:
  *     - js: A list of JavaScript files to load, using the same syntax as Drupal
  *       core's hook_library().
@@ -100,10 +110,10 @@
  *       available or not. The first argument is always $library, an array
  *       containing all library information as described here. The second
  *       argument is always a string containing the variant name. There are two
- *       ways to declare the variant callback's additinal arguments, either as a
+ *       ways to declare the variant callback's additional arguments, either as a
  *       single $options parameter or as multiple parameters, which correspond
  *       to the two ways to specify the argument values (see 'variant
- *       arguments'). If ommitted, the variant is expected to always be
+ *       arguments'). If omitted, the variant is expected to always be
  *       available.
  *     - variant arguments: A list of arguments to pass to the variant callback.
  *       Variant arguments can be declared either as an associative array whose
@@ -112,7 +122,7 @@
  *       the variant callback as a single $options parameter whose keys are the
  *       argument names (i.e. $options is identical to the specified array). If
  *       declared as an indexed array, the array values get passed to the
- *       variant callback as seperate arguments in the order they were declared.
+ *       variant callback as separate arguments in the order they were declared.
  *     Variants can be version-specific (see 'versions').
  *   - versions: (optional) An associative array of supported library versions.
  *     Naturally, libraries evolve over time and so do their APIs. In case a
@@ -146,9 +156,23 @@
  *     Valid callback groups are:
  *     - info: Callbacks registered in this group are applied after the library
  *       information has been retrieved via hook_libraries_info() or info files.
+ *       At this point the following additional information is available:
+ *       - $library['info type']: How the library information was obtained. Can
+ *         be 'info file', 'module', or 'theme', depending on whether the
+ *         library information was obtained from an info file, an enabled module
+ *         or an enabled theme, respectively.
+ *       Additionally, one of the following three keys is available, depending
+ *       on the value of $library['info type'].
+ *       - $library['info file']: In case the library information was obtained
+ *         from an info file, the URI of the info file.
+ *       - $library['module']: In case the library was obtained from an enabled
+ *         module, the name of the providing module.
+ *       - $library['theme']: In case the library was obtained from an enabled
+ *         theme, the name of the providing theme.
  *     - pre-detect: Callbacks registered in this group are applied after the
  *       library path has been determined and before the version callback is
- *       invoked. At this point the following additional information is available:
+ *       invoked. At this point the following additional information is
+ *       available:
  *       - $library['library path']: The path on the file system to the library.
  *     - post-detect: Callbacks registered in this group are applied after the
  *       library has been successfully detected. At this point the library
@@ -299,7 +323,7 @@ function hook_libraries_info() {
         'mymodule_example_libraries_postdetect_callback',
       ),
       // Called before the library's dependencies are loaded.
-      'pre-dependencie-load' => array(
+      'pre-dependencies-load' => array(
         'mymodule_example_libraries_pre_dependencies_load_callback',
       ),
       // Called before the library is loaded.

+ 5 - 3
sites/all/modules/contrib/dev/libraries/libraries.info

@@ -1,11 +1,13 @@
 name = Libraries
 description = Allows version-dependent and shared usage of external libraries.
 core = 7.x
+; We use hook_system_theme_info() which was added in Drupal 7.11
+dependencies[] = system (>=7.11)
 files[] = tests/libraries.test
 
-; Information added by drupal.org packaging script on 2013-03-09
-version = "7.x-2.1"
+; Information added by Drupal.org packaging script on 2014-02-09
+version = "7.x-2.2"
 core = "7.x"
 project = "libraries"
-datestamp = "1362848412"
+datestamp = "1391965716"
 

+ 127 - 23
sites/all/modules/contrib/dev/libraries/libraries.module

@@ -9,6 +9,14 @@
  * Implements hook_flush_caches().
  */
 function libraries_flush_caches() {
+  // Clear static caches.
+  // We don't clear the 'libraries_load' static cache, because that could result
+  // in libraries that had been loaded before the cache flushing to be loaded
+  // again afterwards.
+  foreach (array('libraries_get_path', 'libraries_info') as $name) {
+    drupal_static_reset($name);
+  }
+
   // @todo When upgrading from 1.x, update.php attempts to flush caches before
   //   the cache table has been created.
   // @see http://drupal.org/node/1477932
@@ -133,7 +141,7 @@ function libraries_scan_info_files() {
   $files = array();
   foreach ($directories as $dir) {
     if (file_exists($dir)) {
-      $files = array_merge($files, file_scan_directory($dir, '@^[a-z0-9._-]+\.libraries\.info$@', array(
+      $files = array_merge($files, file_scan_directory($dir, '@^[A-Za-z0-9._-]+\.libraries\.info$@', array(
         'key' => 'name',
         'recurse' => FALSE,
       )));
@@ -158,6 +166,13 @@ function libraries_scan_info_files() {
  *   An array of library information, passed by reference.
  */
 function libraries_invoke($group, &$library) {
+  // When introducing new callback groups in newer versions, stale cached
+  // library information somehow reaches this point during the database update
+  // before clearing the library cache.
+  if (empty($library['callbacks'][$group])) {
+    return;
+  }
+
   foreach ($library['callbacks'][$group] as $callback) {
     libraries_traverse_library($library, $callback);
   }
@@ -333,27 +348,31 @@ function &libraries_info($name = NULL) {
 
   if (!isset($libraries)) {
     $libraries = array();
-    // Gather information from hook_libraries_info().
+
+    // Gather information from hook_libraries_info() in enabled modules.
     foreach (module_implements('libraries_info') as $module) {
       foreach (module_invoke($module, 'libraries_info') as $machine_name => $properties) {
+        $properties['info type'] = 'module';
         $properties['module'] = $module;
         $libraries[$machine_name] = $properties;
       }
     }
+
     // Gather information from hook_libraries_info() in enabled themes.
-    // @see drupal_alter()
-    global $theme, $base_theme_info;
-    if (isset($theme)) {
-      $theme_keys = array();
-      foreach ($base_theme_info as $base) {
-        $theme_keys[] = $base->name;
-      }
-      $theme_keys[] = $theme;
-      foreach ($theme_keys as $theme_key) {
-        $function = $theme_key . '_' . 'libraries_info';
+    $themes = array();
+    foreach (list_themes() as $theme_name => $theme_info) {
+      if ($theme_info->status && file_exists(drupal_get_path('theme', $theme_name) . '/template.php')) {
+        // Collect a list of viable themes for re-use when calling the alter
+        // hook.
+        $themes[] = $theme_name;
+
+        include_once drupal_get_path('theme', $theme_name) . '/template.php';
+
+        $function = $theme_name . '_libraries_info';
         if (function_exists($function)) {
           foreach ($function() as $machine_name => $properties) {
-            $properties['theme'] = $theme_key;
+            $properties['info type'] = 'theme';
+            $properties['theme'] = $theme_name;
             $libraries[$machine_name] = $properties;
           }
         }
@@ -364,6 +383,7 @@ function &libraries_info($name = NULL) {
     // .info files override module definitions.
     foreach (libraries_scan_info_files() as $machine_name => $file) {
       $properties = drupal_parse_info_file($file->uri);
+      $properties['info type'] = 'info file';
       $properties['info file'] = $file->uri;
       $libraries[$machine_name] = $properties;
     }
@@ -373,8 +393,20 @@ function &libraries_info($name = NULL) {
       libraries_info_defaults($properties, $machine_name);
     }
 
-    // Allow modules to alter the registered libraries.
-    drupal_alter('libraries_info', $libraries);
+    // Allow enabled modules and themes to alter the registered libraries.
+    // drupal_alter() only takes the currently active theme into account, not
+    // all enabled themes.
+    foreach (module_implements('libraries_info_alter') as $module) {
+      $function = $module . '_libraries_info_alter';
+      $function($libraries);
+    }
+    foreach ($themes as $theme) {
+      $function = $theme . '_libraries_info_alter';
+      // The template.php file was included above.
+      if (function_exists($function)) {
+        $function($libraries);
+      }
+    }
 
     // Invoke callbacks in the 'info' group.
     foreach ($libraries as &$properties) {
@@ -418,6 +450,8 @@ function libraries_info_defaults(&$library, $name) {
     'versions' => array(),
     'integration files' => array(),
     'callbacks' => array(),
+    // @todo Remove in 7.x-3.x
+    'post-load integration files' => FALSE,
   );
   $library['callbacks'] += array(
     'info' => array(),
@@ -461,9 +495,11 @@ function libraries_detect($name) {
   // Re-use the statically cached value of libraries_info() to save memory.
   $library = &libraries_info($name);
 
+  // Exit early if the library was not found.
   if ($library === FALSE) {
     return $library;
   }
+
   // If 'installed' is set, library detection ran already.
   if (isset($library['installed'])) {
     return $library;
@@ -600,6 +636,12 @@ function libraries_load($name, $variant = NULL) {
       cache_set($name, $library, 'cache_libraries');
     }
 
+    // Exit early if the library was not found.
+    if ($library === FALSE) {
+      $loaded[$name] = $library;
+      return $loaded[$name];
+    }
+
     // If a variant was specified, override the top-level properties with the
     // variant properties.
     if (isset($variant)) {
@@ -652,13 +694,30 @@ function libraries_load($name, $variant = NULL) {
  */
 function libraries_load_files($library) {
   // Load integration files.
-  if (!empty($library['integration files'])) {
-    foreach ($library['integration files'] as $module => $files) {
-      libraries_load_files(array(
-        'files' => $files,
-        'path' => '',
-        'library path' => drupal_get_path('module', $module),
-      ));
+  if (!$library['post-load integration files'] && !empty($library['integration files'])) {
+    $enabled_themes = array();
+    foreach (list_themes() as $theme_name => $theme) {
+      if ($theme->status) {
+        $enabled_themes[] = $theme_name;
+      }
+    }
+    foreach ($library['integration files'] as $provider => $files) {
+      if (module_exists($provider)) {
+        libraries_load_files(array(
+          'files' => $files,
+          'path' => '',
+          'library path' => drupal_get_path('module', $provider),
+          'post-load integration files' => FALSE,
+        ));
+      }
+      elseif (in_array($provider, $enabled_themes)) {
+        libraries_load_files(array(
+          'files' => $files,
+          'path' => '',
+          'library path' => drupal_get_path('theme', $provider),
+          'post-load integration files' => FALSE,
+        ));
+      }
     }
   }
 
@@ -706,15 +765,60 @@ function libraries_load_files($library) {
     foreach ($library['files']['php'] as $file => $array) {
       $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
       if (file_exists($file_path)) {
-        require_once $file_path;
+        _libraries_require_once($file_path);
         $count++;
       }
     }
   }
 
+  // Load integration files.
+  if ($library['post-load integration files'] && !empty($library['integration files'])) {
+    $enabled_themes = array();
+    foreach (list_themes() as $theme_name => $theme) {
+      if ($theme->status) {
+        $enabled_themes[] = $theme_name;
+      }
+    }
+    foreach ($library['integration files'] as $provider => $files) {
+      if (module_exists($provider)) {
+        libraries_load_files(array(
+          'files' => $files,
+          'path' => '',
+          'library path' => drupal_get_path('module', $provider),
+          'post-load integration files' => FALSE,
+        ));
+      }
+      elseif (in_array($provider, $enabled_themes)) {
+        libraries_load_files(array(
+          'files' => $files,
+          'path' => '',
+          'library path' => drupal_get_path('theme', $provider),
+          'post-load integration files' => FALSE,
+        ));
+      }
+    }
+  }
+
   return $count;
 }
 
+/**
+ * Wrapper function for require_once.
+ *
+ * A library file could set a $path variable in file scope. Requiring such a
+ * file directly in libraries_load_files() would lead to the local $path
+ * variable being overridden after the require_once statement. This would
+ * break loading further files. Therefore we use this trivial wrapper which has
+ * no local state that can be tampered with.
+ *
+ * @param $file_path
+ *   The file path of the file to require.
+ */
+function _libraries_require_once($file_path) {
+  require_once $file_path;
+}
+
+
 /**
  * Gets the version information from an arbitrary library.
  *

+ 0 - 11
sites/all/modules/contrib/dev/libraries/tests/example/example_1.css

@@ -1,11 +0,0 @@
-
-/**
- * @file
- * Test CSS file for Libraries loading.
- *
- * Color the 'libraries-test-css' div red. See README.txt for more information.
- */
-
-.libraries-test-css {
-  color: red;
-}

+ 0 - 11
sites/all/modules/contrib/dev/libraries/tests/example/example_2.css

@@ -1,11 +0,0 @@
-
-/**
- * @file
- * Test CSS file for Libraries loading.
- *
- * Color the 'libraries-test-css' div green. See README.txt for more information.
- */
-
-.libraries-test-css {
-  color: green;
-}

+ 0 - 11
sites/all/modules/contrib/dev/libraries/tests/example/example_3.css

@@ -1,11 +0,0 @@
-
-/**
- * @file
- * Test CSS file for Libraries loading.
- *
- * Color the 'libraries-test-css' div orange. See README.txt for more information.
- */
-
-.libraries-test-css {
-  color: orange;
-}

+ 0 - 11
sites/all/modules/contrib/dev/libraries/tests/example/example_4.css

@@ -1,11 +0,0 @@
-
-/**
- * @file
- * Test CSS file for Libraries loading.
- *
- * Color the 'libraries-test-css' div blue. See README.txt for more information.
- */
-
-.libraries-test-css {
-  color: blue;
-}

+ 63 - 27
sites/all/modules/contrib/dev/libraries/tests/libraries.test

@@ -72,7 +72,8 @@ class LibrariesTestCase extends DrupalWebTestCase {
   }
 
   function setUp() {
-    parent::setUp('libraries', 'libraries_test');
+    parent::setUp('libraries', 'libraries_test_module');
+    theme_enable(array('libraries_test_theme'));
   }
 
   /**
@@ -131,6 +132,7 @@ class LibrariesTestCase extends DrupalWebTestCase {
       // FALSE for missing or incompatible dependencies.
       $library['installed'] = TRUE;
       libraries_detect_dependencies($library);
+      $this->verbose('Library:<pre>' . var_export($library, TRUE) . '</pre>');
       $this->assertTrue($library['installed'], "libraries_detect_dependencies() detects compatible version string: '$version_string' is compatible with '$version'");
     }
     foreach ($incompatible as $version_string) {
@@ -138,6 +140,7 @@ class LibrariesTestCase extends DrupalWebTestCase {
       $library['installed'] = TRUE;
       unset($library['error'], $library['error message']);
       libraries_detect_dependencies($library);
+      $this->verbose('Library:<pre>' . var_export($library, TRUE) . '</pre>');
       $this->assertEqual($library['error'], 'incompatible dependency', "libraries_detect_dependencies() detects incompatible version strings: '$version_string' is incompatible with '$version'");
     }
     // Instead of repeating this assertion for each version string, we just
@@ -157,7 +160,7 @@ class LibrariesTestCase extends DrupalWebTestCase {
    */
   function testLibrariesScanInfoFiles() {
     $expected = array('example_info_file' => (object) array(
-      'uri' => drupal_get_path('module', 'libraries') . '/tests/example/example_info_file.libraries.info',
+      'uri' => drupal_get_path('module', 'libraries') . '/tests/libraries/example_info_file.libraries.info',
       'filename' => 'example_info_file.libraries.info',
       'name' => 'example_info_file.libraries',
     ));
@@ -169,17 +172,33 @@ class LibrariesTestCase extends DrupalWebTestCase {
    * Tests libraries_info().
    */
   function testLibrariesInfo() {
+    // Test that modules can provide and alter library information.
+    $info = libraries_info();
+    $this->assertTrue(isset($info['example_module']));
+    $this->verbose('Library:<pre>' . var_export($info['example_module'], TRUE) . '</pre>');
+    $this->assertEqual($info['example_module']['info type'], 'module');
+    $this->assertEqual($info['example_module']['module'], 'libraries_test_module');
+    $this->assertTrue($info['example_module']['module_altered']);
+
+    // Test that themes can provide and alter library information.
+    $this->assertTrue(isset($info['example_theme']));
+    $this->verbose('Library:<pre>' . var_export($info['example_theme'], TRUE) . '</pre>');
+    $this->assertEqual($info['example_theme']['info type'], 'theme');
+    $this->assertEqual($info['example_theme']['theme'], 'libraries_test_theme');
+    $this->assertTrue($info['example_theme']['theme_altered']);
+
     // Test that library information is found correctly.
     $expected = array(
       'name' => 'Example files',
-      'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+      'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
       'version' => '1',
       'files' => array(
         'js' => array('example_1.js' => array()),
         'css' => array('example_1.css' => array()),
         'php' => array('example_1.php' => array()),
       ),
-      'module' => 'libraries_test',
+      'info type' => 'module',
+      'module' => 'libraries_test_module',
     );
     libraries_info_defaults($expected, 'example_files');
     $library = libraries_info('example_files');
@@ -190,7 +209,8 @@ class LibrariesTestCase extends DrupalWebTestCase {
     // Test a library specified with an .info file gets detected.
     $expected = array(
       'name' => 'Example info file',
-      'info file' => drupal_get_path('module', 'libraries_test') . '/example/example_info_file.libraries.info',
+      'info type' => 'info file',
+      'info file' => drupal_get_path('module', 'libraries') . '/tests/libraries/example_info_file.libraries.info',
     );
     libraries_info_defaults($expected, 'example_info_file');
     $library = libraries_info('example_info_file');
@@ -303,6 +323,12 @@ class LibrariesTestCase extends DrupalWebTestCase {
     $loaded = &drupal_static('libraries_load');
     $this->verbose('<pre>' . var_export($loaded, TRUE) . '</pre>');
     $this->assertEqual($loaded['example_dependency']['loaded'], 1, 'Dependency library is also loaded');
+
+    // Test that PHP files that have a local $path variable do not break library
+    // loading.
+    // @see _libraries_require_once()
+    $library = libraries_load('example_path_variable_override');
+    $this->assertEqual($library['loaded'], 2, 'PHP files cannot break library loading.');
   }
 
   /**
@@ -311,7 +337,7 @@ class LibrariesTestCase extends DrupalWebTestCase {
   function testCallbacks() {
     $expected = array(
       'name' => 'Example callback',
-      'library path' => drupal_get_path('module', 'libraries') . '/tests/example',
+      'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example',
       'version' => '1',
       'versions' => array(
         '1' => array(
@@ -344,12 +370,12 @@ class LibrariesTestCase extends DrupalWebTestCase {
         ),
       ),
       'callbacks' => array(
-        'info' => array('_libraries_test_info_callback'),
-        'pre-detect' => array('_libraries_test_pre_detect_callback'),
-        'post-detect' => array('_libraries_test_post_detect_callback'),
-        'pre-dependencies-load' => array('_libraries_test_pre_dependencies_load_callback'),
-        'pre-load' => array('_libraries_test_pre_load_callback'),
-        'post-load' => array('_libraries_test_post_load_callback'),
+        'info' => array('_libraries_test_module_info_callback'),
+        'pre-detect' => array('_libraries_test_module_pre_detect_callback'),
+        'post-detect' => array('_libraries_test_module_post_detect_callback'),
+        'pre-dependencies-load' => array('_libraries_test_module_pre_dependencies_load_callback'),
+        'pre-load' => array('_libraries_test_module_pre_load_callback'),
+        'post-load' => array('_libraries_test_module_post_load_callback'),
       ),
       'info callback' => 'not applied',
       'pre-detect callback' => 'not applied',
@@ -357,7 +383,8 @@ class LibrariesTestCase extends DrupalWebTestCase {
       'pre-dependencies-load callback' => 'not applied',
       'pre-load callback' => 'not applied',
       'post-load callback' => 'not applied',
-      'module' => 'libraries_test',
+      'info type' => 'module',
+      'module' => 'libraries_test_module',
     );
     libraries_info_defaults($expected, 'example_callback');
 
@@ -424,37 +451,46 @@ class LibrariesTestCase extends DrupalWebTestCase {
    * We check for JavaScript and CSS files directly in the DOM and add a list of
    * included PHP files manually to the page output.
    *
-   * @see _libraries_test_load()
+   * @see _libraries_test_module_load()
    */
   function testLibrariesOutput() {
     // Test loading of a simple library with a top-level files property.
-    $this->drupalGet('libraries_test/files');
+    $this->drupalGet('libraries-test-module/files');
     $this->assertLibraryFiles('example_1', 'File loading');
 
     // Test loading of integration files.
-    $this->drupalGet('libraries_test/integration_files');
-    $this->assertRaw('libraries_test.js', 'Integration file loading: libraries_test.js found');
-    $this->assertRaw('libraries_test.css', 'Integration file loading: libraries_test.css found');
-    $this->assertRaw('libraries_test.inc', 'Integration file loading: libraries_test.inc found');
+    $this->drupalGet('libraries-test-module/module-integration-files');
+    $this->assertRaw('libraries_test_module.js', 'Integration file loading: libraries_test_module.js found');
+    $this->assertRaw('libraries_test_module.css', 'Integration file loading: libraries_test_module.css found');
+    $this->assertRaw('libraries_test_module.inc', 'Integration file loading: libraries_test_module.inc found');
+    $this->drupalGet('libraries-test-module/theme-integration-files');
+    $this->assertRaw('libraries_test_theme.js', 'Integration file loading: libraries_test_theme.js found');
+    $this->assertRaw('libraries_test_theme.css', 'Integration file loading: libraries_test_theme.css found');
+    $this->assertRaw('libraries_test_theme.inc', 'Integration file loading: libraries_test_theme.inc found');
+
+    // Test loading of post-load integration files.
+    $this->drupalGet('libraries-test-module/module-integration-files-post-load');
+    // If the files were not loaded correctly, a fatal error occurs.
+    $this->assertResponse(200, 'Post-load integration files are loaded correctly.');
 
     // Test version overloading.
-    $this->drupalGet('libraries_test/versions');
+    $this->drupalGet('libraries-test-module/versions');
     $this->assertLibraryFiles('example_2', 'Version overloading');
 
     // Test variant loading.
-    $this->drupalGet('libraries_test/variant');
+    $this->drupalGet('libraries-test-module/variant');
     $this->assertLibraryFiles('example_3', 'Variant loading');
 
     // Test version overloading and variant loading.
-    $this->drupalGet('libraries_test/versions_and_variants');
+    $this->drupalGet('libraries-test-module/versions-and-variants');
     $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading');
 
     // Test caching.
-    variable_set('libraries_test_cache', TRUE);
+    variable_set('libraries_test_module_cache', TRUE);
     cache_clear_all('example_callback', 'cache_libraries');
     // When the library information is not cached, all callback groups should be
     // invoked.
-    $this->drupalGet('libraries_test/cache');
+    $this->drupalGet('libraries-test-module/cache');
     $this->assertRaw('The <em>info</em> callback group was invoked.', 'Info callback invoked for uncached libraries.');
     $this->assertRaw('The <em>pre-detect</em> callback group was invoked.', 'Pre-detect callback invoked for uncached libraries.');
     $this->assertRaw('The <em>post-detect</em> callback group was invoked.', 'Post-detect callback invoked for uncached libraries.');
@@ -462,13 +498,13 @@ class LibrariesTestCase extends DrupalWebTestCase {
     $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for uncached libraries.');
     // When the library information is cached only the 'pre-load' and
     // 'post-load' callback groups should be invoked.
-    $this->drupalGet('libraries_test/cache');
+    $this->drupalGet('libraries-test-module/cache');
     $this->assertNoRaw('The <em>info</em> callback group was not invoked.', 'Info callback not invoked for cached libraries.');
     $this->assertNoRaw('The <em>pre-detect</em> callback group was not invoked.', 'Pre-detect callback not invoked for cached libraries.');
     $this->assertNoRaw('The <em>post-detect</em> callback group was not invoked.', 'Post-detect callback not invoked for cached libraries.');
     $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for cached libraries.');
     $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for cached libraries.');
-    variable_set('libraries_test_cache', FALSE);
+    variable_set('libraries_test_module_cache', FALSE);
   }
 
   /**
@@ -516,7 +552,7 @@ class LibrariesTestCase extends DrupalWebTestCase {
 
     foreach ($names as $name => $expected) {
       foreach ($extensions as $extension) {
-        $filepath = drupal_get_path('module', 'libraries_test') . "/example/$name.$extension";
+        $filepath = drupal_get_path('module', 'libraries') . "/tests/libraries/example/$name.$extension";
         // JavaScript and CSS files appear as full URLs and with an appended
         // query string.
         if (in_array($extension, array('js', 'css'))) {

+ 9 - 8
sites/all/modules/contrib/dev/libraries/tests/example/README.txt → sites/all/modules/contrib/dev/libraries/tests/libraries/example/README.txt

@@ -14,11 +14,11 @@ CSS and PHP files.
   place the following text on the page:
   "If this text shows up, no JavaScript test file was loaded."
   This text is replaced via JavaScript by a text of the form:
-  "If this text shows up, [[file] was loaded successfully."
+  "If this text shows up, [file] was loaded successfully."
   [file] is either 'example_1.js', 'example_2.js', 'example_3.js',
-  'example_4.js' or 'libraries_test.js'. If you have SimpleTest's verbose mode
-  enabled and see the above text in one of the debug pages, the noted JavaScript
-  file was loaded successfully.
+  'example_4.js' or 'libraries_test_module.js'. If you have SimpleTest's verbose
+  mode enabled and see the above text in one of the debug pages, the noted
+  JavaScript file was loaded successfully.
 - CSS: The filenames of the CSS files are asserted to be in the raw HTML via
   SimpleTest. Since the filename could appear, for instance, in an error
   message, this is not very robust. Explicit testing of CSS, though, is not yet
@@ -29,7 +29,7 @@ CSS and PHP files.
   - example_2: green
   - example_3: orange
   - example_4: blue
-  - libraries_test: purple"
+  - libraries_test_module: purple"
   If you have SimpleTest's verbose mode enabled, and see the above text in a
   certain color (i.e. not in black), a CSS file was loaded successfully. Which
   file depends on the color as referenced in the text above.
@@ -37,6 +37,7 @@ CSS and PHP files.
   PHP files and then checking whether this function was defined using
   function_exists(). This can be checked programatically with SimpleTest.
 The loading of integration files is tested with the same method. The integration
-files are libraries_test.js, libraries_test.css, libraries_test.inc and are
-located in the tests directory alongside libraries_test.module (i.e. they are
-not in the same directory as this file).
+files are libraries_test_module.js, libraries_test_module.css,
+libraries_test_module.inc and are located in the test module's directory
+alongside libraries_test_module.info (i.e. they are not in the same directory as
+this file).

+ 12 - 0
sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.css

@@ -0,0 +1,12 @@
+
+/**
+ * @file
+ * Test CSS file for Libraries loading.
+ *
+ * Color the 'libraries-test-module-css' div red. See README.txt for more
+ * information.
+ */
+
+.libraries-test-module-css {
+  color: red;
+}

+ 2 - 2
sites/all/modules/contrib/dev/libraries/tests/example/example_1.js → sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.js

@@ -3,7 +3,7 @@
  * @file
  * Test JavaScript file for Libraries loading.
  *
- * Replace the text in the 'libraries-test-javascript' div. See README.txt for
+ * Replace the text in the 'libraries-test-module-js' div. See README.txt for
  * more information.
  */
 
@@ -11,7 +11,7 @@
 
 Drupal.behaviors.librariesTest = {
   attach: function(context, settings) {
-    $('.libraries-test-javascript').text('If this text shows up, example_1.js was loaded successfully.')
+    $('.libraries-test-module-js').text('If this text shows up, example_1.js was loaded successfully.')
   }
 };
 

Some files were not shown because too many files changed in this diff