From 37fbabab56587d691c4377e53f9a488f4442e5fe Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Mon, 20 Apr 2015 16:32:07 +0200 Subject: [PATCH] non security modules update --- .../admin/date_popup_authored/.travis.yml | 57 + .../date_popup_authored/CHANGELOG.markdown | 31 + .../admin/date_popup_authored/CHANGELOG.txt | 30 - .../admin/date_popup_authored/README.markdown | 78 + .../admin/date_popup_authored/README.txt | 76 - .../date_popup_authored.info | 9 +- .../date_popup_authored.install | 17 + .../date_popup_authored.module | 93 +- .../date_popup_authored_format.test} | 78 +- .../contrib/admin/features/features.admin.inc | 147 +- .../contrib/admin/features/features.api.php | 56 + .../contrib/admin/features/features.css | 13 + .../contrib/admin/features/features.drush.inc | 120 +- .../admin/features/features.export.inc | 27 +- .../contrib/admin/features/features.info | 6 +- .../contrib/admin/features/features.install | 12 +- .../contrib/admin/features/features.js | 28 - .../contrib/admin/features/features.module | 112 +- .../features/includes/features.field.inc | 40 +- .../features/includes/features.image.inc | 31 +- .../features/includes/features.locale.inc | 1 + .../admin/features/includes/features.menu.inc | 49 +- .../admin/features/includes/features.node.inc | 12 +- .../admin/features/includes/features.user.inc | 4 +- .../features_test/features_test.features.inc | 8 - .../tests/features_test/features_test.info | 6 +- .../theme/features-admin-components.tpl.php | 2 +- .../contrib/admin/features/theme/theme.inc | 31 + .../admin/filter_perms/filter_perms.info | 6 +- .../contrib/admin/google_analytics/README.txt | 29 +- .../googleanalytics.admin.inc | 469 ++++-- .../google_analytics/googleanalytics.admin.js | 2 +- .../google_analytics/googleanalytics.debug.js | 183 ++ .../google_analytics/googleanalytics.info | 8 +- .../google_analytics/googleanalytics.install | 149 +- .../admin/google_analytics/googleanalytics.js | 134 +- .../google_analytics/googleanalytics.module | 296 ++-- .../google_analytics/googleanalytics.test | 543 ++++-- .../google_analytics/googleanalytics.test.js | 119 ++ .../googleanalytics.variable.inc | 3 +- .../contrib/admin/module_filter/CHANGELOG.txt | 216 ++- .../contrib/admin/module_filter/README.txt | 107 ++ .../module_filter/css/dynamic_position.css | 23 + .../admin/module_filter/css/module_filter.css | 18 +- .../css/module_filter_tab-rtl.css | 54 + .../module_filter/css/module_filter_tab.css | 358 ++-- .../admin/module_filter/css/modules.css | 47 + .../admin/module_filter/css/update_status.css | 18 + .../module_filter/js/dynamic_position.js | 57 +- .../admin/module_filter/js/module_filter.js | 335 +++- .../module_filter/js/module_filter_tab.js | 783 ++++++--- .../contrib/admin/module_filter/js/modules.js | 176 ++ .../admin/module_filter/js/permissions.js | 67 + .../admin/module_filter/js/update_status.js | 117 ++ .../module_filter/module_filter.admin.inc | 69 +- .../admin/module_filter/module_filter.info | 7 +- .../admin/module_filter/module_filter.install | 27 +- .../admin/module_filter/module_filter.module | 256 ++- .../module_filter/module_filter.pages.inc | 46 + .../module_filter/module_filter.theme.inc | 237 ++- .../admin/override_node_options/LICENSE.txt | 521 +++--- .../override_node_options.info | 7 +- .../override_node_options.install | 22 + .../override_node_options.module | 18 +- .../all/modules/contrib/dev/devel/README.txt | 5 +- .../modules/contrib/dev/devel/devel.admin.inc | 6 +- .../modules/contrib/dev/devel/devel.drush.inc | 60 +- .../all/modules/contrib/dev/devel/devel.info | 6 +- .../modules/contrib/dev/devel/devel.install | 31 +- sites/all/modules/contrib/dev/devel/devel.js | 10 +- .../modules/contrib/dev/devel/devel.mail.inc | 5 +- .../modules/contrib/dev/devel/devel.module | 594 ++++--- .../modules/contrib/dev/devel/devel.pages.inc | 19 +- .../all/modules/contrib/dev/devel/devel.test | 3 +- .../devel_generate/devel_generate.drush.inc | 58 +- .../devel/devel_generate/devel_generate.inc | 140 +- .../devel/devel_generate/devel_generate.info | 8 +- .../devel_generate/devel_generate.module | 93 +- .../devel/devel_generate/devel_generate.test | 108 ++ .../devel_generate/file.devel_generate.inc | 2 +- .../devel_generate/image.devel_generate.inc | 6 +- .../devel_generate/text.devel_generate.inc | 8 +- .../contrib/dev/devel/devel_krumo_path.js | 2 +- .../contrib/dev/devel/devel_node_access.info | 6 +- .../contrib/dev/devel/devel_node_access.js | 14 +- .../dev/devel/devel_node_access.module | 59 +- .../contrib/dev/devel/krumo/class.krumo.php | 3 +- .../all/modules/contrib/dev/devel/runtests.sh | 22 + .../contrib/dev/libraries/CHANGELOG.txt | 28 +- .../contrib/dev/libraries/libraries.api.php | 60 +- .../contrib/dev/libraries/libraries.info | 8 +- .../contrib/dev/libraries/libraries.module | 150 +- .../dev/libraries/tests/example/example_1.css | 11 - .../dev/libraries/tests/example/example_2.css | 11 - .../dev/libraries/tests/example/example_3.css | 11 - .../dev/libraries/tests/example/example_4.css | 11 - .../dev/libraries/tests/libraries.test | 90 +- .../tests/{ => libraries}/example/README.txt | 17 +- .../tests/libraries/example/example_1.css | 12 + .../{ => libraries}/example/example_1.js | 4 +- .../tests/libraries/example/example_1.php | 15 + .../tests/libraries/example/example_2.css | 12 + .../{ => libraries}/example/example_2.js | 4 +- .../tests/libraries/example/example_2.php | 15 + .../tests/libraries/example/example_3.css | 12 + .../{ => libraries}/example/example_3.js | 4 +- .../{ => libraries}/example/example_3.php | 2 +- .../tests/libraries/example/example_4.css | 12 + .../{ => libraries}/example/example_4.js | 4 +- .../{ => libraries}/example/example_4.php | 2 +- .../example_info_file.libraries.info | 7 +- .../dev/libraries/tests/libraries_test.css | 12 - .../dev/libraries/tests/libraries_test.inc | 11 - .../dev/libraries/tests/libraries_test.js | 18 - .../libraries_test_module.css | 12 + .../libraries_test_module.inc} | 3 +- .../libraries_test_module.info} | 7 +- .../libraries_test_module.js | 18 + .../libraries_test_module.module} | 229 ++- .../libraries_test_module_post_load.inc | 15 + .../libraries_test_theme.css | 12 + .../libraries_test_theme.inc} | 3 +- .../libraries_test_theme.info | 11 + .../libraries_test_theme.js | 18 + .../themes/libraries_test_theme/template.php | 36 + .../contrib/dev/performance/README.txt | 27 + .../includes/performance.details.inc | 137 ++ .../contrib/dev/performance/performance.info | 6 +- .../dev/performance/performance.module | 117 +- .../editor/pathologic/pathologic.api.php | 2 +- .../contrib/editor/pathologic/pathologic.info | 6 +- .../editor/pathologic/pathologic.module | 72 +- .../contrib/fields/email/email.diff.inc | 17 + .../modules/contrib/fields/email/email.info | 6 +- .../contrib/fields/email/email.migrate.inc | 2 +- .../modules/contrib/fields/email/email.module | 15 +- .../modules/contrib/fields/link/link-rtl.css | 8 + .../fields/link/link.devel_generate.inc | 7 +- .../all/modules/contrib/fields/link/link.info | 6 +- .../contrib/fields/link/link.migrate.inc | 7 +- .../modules/contrib/fields/link/link.module | 305 +++- .../fields/link/tests/link.attribute.test | 58 +- .../contrib/fields/link/tests/link.crud.test | 14 +- .../fields/link/tests/link.crud_browser.test | 101 +- .../contrib/fields/link/tests/link.test | 4 +- .../contrib/fields/link/tests/link.token.test | 92 +- .../fields/link/tests/link.validate.test | 56 +- .../link_views_handler_argument_target.inc | 2 +- .../link_views_handler_filter_protocol.inc | 4 +- .../modules/contrib/files/imce/imce/imce.info | 6 +- .../contrib/files/imce/imce/imce.install | 20 + .../files/imce/imce/inc/imce.admin.inc | 57 +- .../contrib/files/imce/imce/inc/imce.page.inc | 3 +- .../contrib/files/imce/imce/js/imce.js | 53 +- .../contrib/files/imce/imce/js/imce_extras.js | 12 +- .../files/imce/imce/js/imce_set_app.js | 2 +- .../files/imce/imce/js/imce_set_inline.js | 2 +- .../contrib/files/imce/imce/js/jquery.form.js | 31 +- .../files/imce/imce/tpl/imce-page.tpl.php | 1 + .../{modules => }/imce_dir_man/README.txt | 0 .../imce_dir_man/imce_dir_man.info | 6 +- .../imce_dir_man/imce_dir_man.install | 0 .../imce_dir_man/imce_dir_man.module | 0 .../{modules => }/imce_file_path/README.txt | 0 .../imce_file_path/imce_file_path.css | 0 .../imce_file_path/imce_file_path.info | 6 +- .../imce_file_path/imce_file_path.install | 0 .../imce_file_path/imce_file_path.js | 0 .../imce_file_path/imce_file_path.module | 0 .../{modules => }/imce_search/README.txt | 0 .../{modules => }/imce_search/imce_search.css | 4 + .../imce_search/imce_search.info | 6 +- .../imce_search/imce_search.install | 0 .../{modules => }/imce_search/imce_search.js | 8 +- .../imce_search/imce_search.module | 0 .../{modules => }/imce_search/loading.gif | Bin .../contrib/form/honeypot/honeypot.admin.inc | 189 ++- .../contrib/form/honeypot/honeypot.api.php | 23 +- .../contrib/form/honeypot/honeypot.info | 6 +- .../contrib/form/honeypot/honeypot.install | 7 +- .../contrib/form/honeypot/honeypot.module | 92 +- .../contrib/form/honeypot/honeypot.test | 113 +- .../entity_translation/CHANGELOG.txt | 47 +- .../entity_translation.admin.inc | 8 +- .../entity_translation.api.php | 15 +- .../entity_translation.info | 8 +- .../entity_translation.install | 194 ++- .../entity_translation.module | 356 ++-- .../entity_translation_i18n_menu.info | 6 +- .../entity_translation_upgrade.info | 6 +- .../includes/translation.handler.inc | 359 +++- .../includes/translation.handler_factory.inc | 139 ++ .../plugins/access/translation_exists.inc | 142 ++ .../tests/entity_translation.test | 6 +- .../tests/entity_translation_test.info | 6 +- .../views/entity_translation.views.inc | 20 + ...entity_translation_handler_field_field.inc | 65 + .../localisation/l10n_update/README.txt | 83 +- .../l10n_update/css/l10n_update.admin-rtl.css | 32 +- .../l10n_update/css/l10n_update.admin.css | 108 +- .../l10n_update/includes/gettext/PoHeader.php | 418 +++++ .../l10n_update/includes/gettext/PoItem.php | 282 ++++ .../includes/gettext/PoMemoryWriter.php | 90 + .../includes/gettext/PoMetadataInterface.php | 48 + .../includes/gettext/PoReaderInterface.php | 21 + .../includes/gettext/PoStreamInterface.php | 42 + .../includes/gettext/PoStreamReader.php | 603 +++++++ .../includes/gettext/PoStreamWriter.php | 160 ++ .../includes/gettext/PoWriterInterface.php | 32 + .../l10n_update/includes/locale/Gettext.php | 97 ++ .../includes/locale/PoDatabaseReader.php | 178 ++ .../includes/locale/PoDatabaseWriter.php | 296 ++++ .../includes/locale/SourceString.php | 52 + .../includes/locale/StringBase.php | 184 ++ .../includes/locale/StringDatabaseStorage.php | 518 ++++++ .../includes/locale/StringInterface.php | 180 ++ .../locale/StringStorageException.php | 11 + .../locale/StringStorageInterface.php | 165 ++ .../includes/locale/TranslationString.php | 126 ++ .../locale/TranslationsStreamWrapper.php | 27 + .../l10n_update/js/l10n_update.admin.js | 37 + .../l10n_update/js/l10n_update.js | 22 - ...l10n_update-translation-last-check.tpl.php | 18 + ...10n_update-translation-update-info.tpl.php | 31 + .../l10n_update/l10n_update.admin.inc | 832 +++++---- .../l10n_update/l10n_update.api.php | 30 +- .../l10n_update/l10n_update.batch.inc | 418 +++-- .../l10n_update/l10n_update.bulk.inc | 726 ++++++++ .../l10n_update/l10n_update.check.inc | 488 ------ .../l10n_update/l10n_update.compare.inc | 386 +++++ .../l10n_update/l10n_update.drush.inc | 191 ++- .../l10n_update/l10n_update.fetch.inc | 108 ++ .../{l10n_update.inc => l10n_update.http.inc} | 355 +--- .../localisation/l10n_update/l10n_update.info | 42 +- .../l10n_update/l10n_update.install | 197 ++- .../l10n_update/l10n_update.locale.inc | 463 ----- .../l10n_update/l10n_update.module | 780 ++++++--- .../l10n_update/l10n_update.parser.inc | 134 -- .../l10n_update/l10n_update.project.inc | 243 --- .../l10n_update/l10n_update.translation.inc | 570 +++++++ .../l10n_update/tests/L10nUpdateCronTest.test | 115 ++ .../tests/L10nUpdateInterfaceTest.test | 91 + .../l10n_update/tests/L10nUpdateTest.test | 440 +++++ .../l10n_update/tests/L10nUpdateTestBase.test | 284 ++++ .../l10n_update_test/l10n_update_test.info | 14 + .../l10n_update_test/l10n_update_test.install | 15 + .../l10n_update_test/l10n_update_test.module | 143 ++ .../l10n_update_test_translate.info | 16 + .../l10n_update_test_translate.module | 28 + .../l10n_update_test_translate.de.po | 28 + .../l10n_update_test_translate.nl.po | 31 + .../l10n_update_test_translate.xx.po | 40 + .../localisation/l10n_update/tests/test.de.po | 10 + .../mail/maillog/includes/maillog.mail.inc | 6 +- .../includes/maillog.views_default.inc | 2 +- .../modules/contrib/mail/maillog/maillog.info | 6 +- .../contrib/mail/maillog/maillog.module | 12 +- .../mandrill_simplenews_report.info | 4 +- .../contrib/migrate/migrate/CHANGELOG.txt | 248 ++- .../contrib/migrate/migrate/README.txt | 4 +- .../contrib/migrate/migrate/includes/base.inc | 423 ++++- .../migrate/migrate/includes/destination.inc | 17 +- .../migrate/migrate/includes/exception.inc | 19 +- .../migrate/includes/field_mapping.inc | 17 + .../migrate/migrate/includes/group.inc | 145 +- .../contrib/migrate/migrate/includes/map.inc | 19 +- .../migrate/migrate/includes/migration.inc | 248 ++- .../migrate/migrate/includes/source.inc | 149 +- .../contrib/migrate/migrate/migrate.api.php | 81 +- .../contrib/migrate/migrate/migrate.drush.inc | 304 +++- .../contrib/migrate/migrate/migrate.info | 13 +- .../contrib/migrate/migrate/migrate.install | 242 ++- .../contrib/migrate/migrate/migrate.module | 373 ++-- .../migrate/migrate/migrate_example/beer.inc | 335 ++-- .../migrate/migrate_example/beer.install.inc | 5 +- .../migrate_example/migrate_example.info | 9 +- .../migrate_example/migrate_example.install | 1 + .../migrate_example.migrate.inc | 255 ++- .../migrate_example/migrate_example.module | 8 +- .../migrate_example_oracle.info | 8 +- .../migrate/migrate/migrate_example/wine.inc | 1500 +++++++++++------ .../migrate/migrate_example/wine.install.inc | 23 +- .../migrate/migrate_example/xml/0002.xml | 7 + .../migrate/migrate_example/xml/index2.xml | 4 + .../migrate_example/xml/producers3.xml | 17 + .../migrate_example/xml/producers4.xml | 17 + .../migrate_example_baseball.features.inc | 2 +- .../migrate_example_baseball.info | 8 +- .../migrate_example_baseball.install | 13 +- .../migrate_example_baseball.migrate.inc | 22 +- .../migrate/migrate_ui/migrate_ui.info | 12 +- .../migrate/migrate_ui/migrate_ui.install | 71 + .../migrate/migrate_ui/migrate_ui.module | 215 ++- .../migrate/migrate_ui/migrate_ui.pages.inc | 1493 +++++++++++++--- .../migrate/migrate_ui/migrate_ui.wizard.inc | 663 ++++++++ .../plugins/destinations/block_custom.inc | 237 +++ .../migrate/plugins/destinations/comment.inc | 93 +- .../migrate/plugins/destinations/entity.inc | 5 + .../migrate/plugins/destinations/fields.inc | 181 +- .../migrate/plugins/destinations/file.inc | 258 +-- .../migrate/plugins/destinations/node.inc | 180 +- .../migrate/plugins/destinations/path.inc | 31 +- .../migrate/plugins/destinations/poll.inc | 19 +- .../plugins/destinations/statistics.inc | 20 +- .../migrate/plugins/destinations/table.inc | 57 +- .../plugins/destinations/table_copy.inc | 11 +- .../migrate/plugins/destinations/term.inc | 42 +- .../migrate/plugins/destinations/user.inc | 53 +- .../migrate/plugins/destinations/variable.inc | 187 ++ .../migrate/migrate/plugins/sources/csv.inc | 2 + .../migrate/migrate/plugins/sources/db2.inc | 163 ++ .../migrate/migrate/plugins/sources/files.inc | 27 +- .../migrate/migrate/plugins/sources/json.inc | 4 +- .../migrate/migrate/plugins/sources/list.inc | 25 + .../migrate/plugins/sources/mongodb.inc | 189 +++ .../migrate/migrate/plugins/sources/mssql.inc | 3 +- .../migrate/plugins/sources/multiitems.inc | 27 +- .../migrate/plugins/sources/spreadsheet.inc | 238 +++ .../migrate/migrate/plugins/sources/sql.inc | 148 +- .../migrate/plugins/sources/sqlmap.inc | 66 +- .../migrate/migrate/plugins/sources/xml.inc | 733 +++++--- .../migrate/migrate/tests/import/options.test | 2 +- .../tests/plugins/destinations/comment.test | 2 +- .../tests/plugins/destinations/node.test | 2 +- .../tests/plugins/destinations/table.test | 2 +- .../tests/plugins/destinations/term.test | 2 +- .../tests/plugins/destinations/user.test | 15 +- .../migrate/tests/plugins/sources/oracle.test | 2 +- .../migrate/tests/plugins/sources/xml.test | 67 +- .../contrib/panels/panels/css/panels_dnd.css | 17 +- .../panels/panels/i18n_panels/README.txt | 93 + .../panels/i18n_panels/i18n_panels.i18n.inc | 50 + .../panels/i18n_panels/i18n_panels.info | 15 + .../panels/i18n_panels/i18n_panels.install | 27 + .../panels/i18n_panels/i18n_panels.module | 442 +++++ .../panels/panels/includes/add-content.inc | 9 +- .../panels/panels/includes/callbacks.inc | 16 + .../contrib/panels/panels/includes/common.inc | 1 + .../panels/panels/includes/display-edit.inc | 2 +- .../panels/panels/includes/display-layout.inc | 1 + .../panels/panels/includes/plugins.inc | 37 +- .../panels/panels/js/display_editor.js | 13 +- .../contrib/panels/panels/js/panels.js | 28 - .../contrib/panels/panels/panels.api.php | 264 +++ .../modules/contrib/panels/panels/panels.info | 9 +- .../contrib/panels/panels/panels.install | 139 +- .../contrib/panels/panels/panels.module | 243 ++- .../panels/panels_ipe/css/panels_ipe-rtl.css | 67 + .../panels/panels_ipe/css/panels_ipe.css | 47 +- .../panels/panels/panels_ipe/js/panels_ipe.js | 98 +- .../panels/panels/panels_ipe/panels_ipe.info | 7 +- .../panels/panels_ipe/panels_ipe.module | 84 +- .../panels_renderer_ipe.class.php | 38 +- .../panels/panels/panels_mini/panels_mini.css | 12 + .../panels/panels_mini/panels_mini.info | 7 +- .../panels/panels_mini/panels_mini.module | 57 +- .../plugins/content_types/panels_mini.inc | 37 + .../export_ui/panels_mini_ui.class.php | 7 + .../panels/panels_node/panels_node.info | 7 +- .../panels/panels_node/panels_node.module | 57 + .../panels/panels/plugins/cache/simple.inc | 161 ++ .../panels_renderer_editor.class.php | 209 ++- .../panels_renderer_standard.class.php | 61 +- .../export_ui/panels_layouts_ui.class.php | 10 + .../layouts/flexible/flexible-admin.js | 5 +- .../plugins/layouts/flexible/flexible.inc | 54 +- .../threecol_25_50_25_stacked.css | 2 +- .../panels/panels/plugins/styles/block.inc | 3 + .../plugins/task_handlers/panel_context.inc | 85 +- .../views/panels_views_plugin_row_fields.inc | 4 +- .../templates/panels-add-content-link.tpl.php | 24 +- .../panels/templates/panels-pane.tpl.php | 6 +- .../feed_path_publisher.info | 7 +- .../modules/contrib/seo/metatag/CHANGELOG.txt | 228 ++- .../modules/contrib/seo/metatag/README.txt | 134 +- .../contrib/seo/metatag/metatag.admin.css | 6 + .../contrib/seo/metatag/metatag.admin.inc | 136 +- .../contrib/seo/metatag/metatag.admin.js | 12 +- .../contrib/seo/metatag/metatag.api.php | 92 +- .../contrib/seo/metatag/metatag.drush.inc | 21 + .../contrib/seo/metatag/metatag.features.inc | 3 + .../contrib/seo/metatag/metatag.feeds.inc | 30 +- .../modules/contrib/seo/metatag/metatag.inc | 111 +- .../modules/contrib/seo/metatag/metatag.info | 11 +- .../contrib/seo/metatag/metatag.install | 743 +++++++- .../contrib/seo/metatag/metatag.metatag.inc | 113 +- .../contrib/seo/metatag/metatag.migrate.inc | 6 +- .../contrib/seo/metatag/metatag.module | 944 ++++++++--- .../modules/contrib/seo/metatag/metatag.test | 167 +- .../contrib/seo/metatag/metatag.theme.inc | 13 + .../contrib/seo/metatag/metatag.tokens.inc | 69 +- .../seo/metatag/metatag.vertical-tabs.js | 14 +- .../metatag_context/metatag_context.admin.inc | 13 +- .../metatag_context.context.inc | 41 +- .../metatag_context/metatag_context.info | 7 +- .../metatag_context/metatag_context.test | 10 +- .../contrib/seo/metatag/metatag_dc/README.txt | 5 +- .../seo/metatag/metatag_dc/metatag_dc.info | 6 +- .../metatag/metatag_dc/metatag_dc.metatag.inc | 77 +- .../metatag/metatag_devel/metatag_devel.info | 13 + .../metatag_devel/metatag_devel.module | 176 ++ .../metatag_facebook/metatag_facebook.info | 12 + .../metatag_facebook/metatag_facebook.install | 5 + .../metatag_facebook.metatag.inc | 54 + .../metatag_facebook/metatag_facebook.module | 26 + .../metatag/metatag_google_plus/README.txt | 60 + .../metatag_google_plus.inc | 25 + .../metatag_google_plus.info | 15 + .../metatag_google_plus.metatag.inc | 135 ++ .../metatag_google_plus.module | 60 + .../metatag_opengraph/metatag_opengraph.info | 10 +- .../metatag_opengraph.install | 19 + .../metatag_opengraph.metatag.inc | 682 +++++--- .../metatag_opengraph.module | 36 +- .../metatag_panels/metatag_panels.info | 6 +- .../metatag_panels/metatag_panels.module | 38 +- .../metatag/metatag_twitter_cards/README.txt | 2 +- .../metatag_twitter_cards.info | 6 +- .../metatag_twitter_cards.metatag.inc | 395 ++++- .../metatag_twitter_cards.module | 2 +- .../seo/metatag/metatag_ui/metatag_ui.info | 14 - .../seo/metatag/metatag_ui/metatag_ui.module | 5 - .../metatag/metatag_views/metatag_views.info | 6 +- .../metatag_views/metatag_views.module | 107 +- .../metatag_views/metatag_views.views.inc | 1 + ...views_plugin_display_extender_metatags.inc | 85 +- .../seo/metatag/tests/metatag_test.info | 6 +- .../contrib/theming/extlink/extlink.css | 10 +- .../contrib/theming/extlink/extlink.info | 6 +- .../contrib/theming/extlink/extlink.install | 13 +- .../contrib/theming/extlink/extlink.js | 147 +- .../contrib/theming/extlink/extlink.module | 41 +- .../contrib/theming/extlink/extlink.test | 100 +- .../contrib/theming/extlink/extlink_s.png | Bin 0 -> 153 bytes .../users/email_registration/README.txt | 3 - .../email_registration.api.php | 13 +- .../email_registration.info | 6 +- .../email_registration.module | 91 +- .../email_registration.test | 16 +- .../all/modules/contrib/users/faq/.gitignore | 4 + .../modules/contrib/users/faq/faq.admin.inc | 38 +- sites/all/modules/contrib/users/faq/faq.info | 8 +- .../all/modules/contrib/users/faq/faq.install | 339 +++- sites/all/modules/contrib/users/faq/faq.js | 16 +- .../all/modules/contrib/users/faq/faq.module | 534 ++++-- sites/all/modules/contrib/users/faq/faq.test | 25 +- .../contrib/users/faq/faq.variable.inc | 6 +- .../includes/faq-category-hide-answer.tpl.php | 5 +- .../includes/faq-category-new-page.tpl.php | 6 +- .../faq-category-questions-inline.tpl.php | 6 +- ...faq-category-questions-top-answers.tpl.php | 11 +- .../faq-category-questions-top.tpl.php | 15 +- .../faq/includes/faq-hide-answer.tpl.php | 2 +- .../users/faq/includes/faq-new-page.tpl.php | 3 +- .../faq/includes/faq-questions-inline.tpl.php | 1 - .../faq/includes/faq-questions-top.tpl.php | 7 +- .../users/faq/includes/faq.hide_answer.inc | 8 +- .../users/faq/includes/faq.new_page.inc | 17 +- .../faq/includes/faq.questions_inline.inc | 8 +- .../users/faq/includes/faq.questions_top.inc | 26 +- .../contrib/users/faq/views/faq.views.inc | 11 +- .../users/faq/views/faq.views_default.inc | 3 +- .../users/friendly_register/README.txt | 19 +- .../friendly_register/friendly_register.info | 6 +- .../friendly_register.module | 28 +- .../friendly_register/js/friendly_register.js | 6 +- 466 files changed, 32690 insertions(+), 9652 deletions(-) create mode 100644 sites/all/modules/contrib/admin/date_popup_authored/.travis.yml create mode 100644 sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.markdown delete mode 100644 sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.txt create mode 100644 sites/all/modules/contrib/admin/date_popup_authored/README.markdown delete mode 100644 sites/all/modules/contrib/admin/date_popup_authored/README.txt create mode 100644 sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.install rename sites/all/modules/contrib/admin/date_popup_authored/{date_popup_authored.test => tests/date_popup_authored_format.test} (60%) create mode 100644 sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js create mode 100644 sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js create mode 100644 sites/all/modules/contrib/admin/module_filter/README.txt create mode 100644 sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css create mode 100644 sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css create mode 100644 sites/all/modules/contrib/admin/module_filter/css/modules.css create mode 100644 sites/all/modules/contrib/admin/module_filter/css/update_status.css create mode 100644 sites/all/modules/contrib/admin/module_filter/js/modules.js create mode 100644 sites/all/modules/contrib/admin/module_filter/js/permissions.js create mode 100644 sites/all/modules/contrib/admin/module_filter/js/update_status.js create mode 100644 sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc create mode 100644 sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test create mode 100644 sites/all/modules/contrib/dev/devel/runtests.sh delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/example/example_1.css delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/example/example_2.css delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/example/example_3.css delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/example/example_4.css rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/README.txt (76%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.css rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/example_1.js (52%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.php create mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_2.css rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/example_2.js (52%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_2.php create mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_3.css rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/example_3.js (52%) rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/example_3.php (73%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_4.css rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/example_4.js (52%) rename sites/all/modules/contrib/dev/libraries/tests/{ => libraries}/example/example_4.php (73%) rename sites/all/modules/contrib/dev/libraries/tests/{example => libraries}/example_info_file.libraries.info (55%) delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries_test.css delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries_test.inc delete mode 100644 sites/all/modules/contrib/dev/libraries/tests/libraries_test.js create mode 100644 sites/all/modules/contrib/dev/libraries/tests/modules/libraries_test_module/libraries_test_module.css rename sites/all/modules/contrib/dev/libraries/tests/{example/example_2.php => modules/libraries_test_module/libraries_test_module.inc} (69%) rename sites/all/modules/contrib/dev/libraries/tests/{libraries_test.info => modules/libraries_test_module/libraries_test_module.info} (57%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/modules/libraries_test_module/libraries_test_module.js rename sites/all/modules/contrib/dev/libraries/tests/{libraries_test.module => modules/libraries_test_module/libraries_test_module.module} (68%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc create mode 100644 sites/all/modules/contrib/dev/libraries/tests/themes/libraries_test_theme/libraries_test_theme.css rename sites/all/modules/contrib/dev/libraries/tests/{example/example_1.php => themes/libraries_test_theme/libraries_test_theme.inc} (69%) create mode 100644 sites/all/modules/contrib/dev/libraries/tests/themes/libraries_test_theme/libraries_test_theme.info create mode 100644 sites/all/modules/contrib/dev/libraries/tests/themes/libraries_test_theme/libraries_test_theme.js create mode 100644 sites/all/modules/contrib/dev/libraries/tests/themes/libraries_test_theme/template.php create mode 100644 sites/all/modules/contrib/dev/performance/includes/performance.details.inc create mode 100644 sites/all/modules/contrib/fields/email/email.diff.inc create mode 100644 sites/all/modules/contrib/fields/link/link-rtl.css rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_dir_man/README.txt (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_dir_man/imce_dir_man.info (69%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_dir_man/imce_dir_man.install (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_dir_man/imce_dir_man.module (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_file_path/README.txt (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_file_path/imce_file_path.css (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_file_path/imce_file_path.info (66%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_file_path/imce_file_path.install (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_file_path/imce_file_path.js (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_file_path/imce_file_path.module (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/README.txt (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/imce_search.css (78%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/imce_search.info (64%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/imce_search.install (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/imce_search.js (91%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/imce_search.module (100%) rename sites/all/modules/contrib/files/imce/imce_tools/{modules => }/imce_search/loading.gif (100%) create mode 100644 sites/all/modules/contrib/localisation/entity_translation/includes/translation.handler_factory.inc create mode 100644 sites/all/modules/contrib/localisation/entity_translation/plugins/access/translation_exists.inc create mode 100644 sites/all/modules/contrib/localisation/entity_translation/views/entity_translation_handler_field_field.inc create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoHeader.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoItem.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoMemoryWriter.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoMetadataInterface.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoReaderInterface.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoStreamInterface.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoStreamReader.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoStreamWriter.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/gettext/PoWriterInterface.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/Gettext.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/PoDatabaseReader.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/PoDatabaseWriter.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/SourceString.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/StringBase.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/StringDatabaseStorage.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/StringInterface.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/StringStorageException.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/StringStorageInterface.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/TranslationString.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/includes/locale/TranslationsStreamWrapper.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/js/l10n_update.admin.js delete mode 100644 sites/all/modules/contrib/localisation/l10n_update/js/l10n_update.js create mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update-translation-last-check.tpl.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update-translation-update-info.tpl.php create mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.bulk.inc delete mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.check.inc create mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.compare.inc create mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.fetch.inc rename sites/all/modules/contrib/localisation/l10n_update/{l10n_update.inc => l10n_update.http.inc} (54%) delete mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.locale.inc delete mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.parser.inc delete mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.project.inc create mode 100644 sites/all/modules/contrib/localisation/l10n_update/l10n_update.translation.inc create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/L10nUpdateCronTest.test create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/L10nUpdateInterfaceTest.test create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/L10nUpdateTest.test create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/L10nUpdateTestBase.test create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test/l10n_update_test.info create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test/l10n_update_test.install create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test/l10n_update_test.module create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.info create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test_translate/l10n_update_test_translate.module create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.de.po create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.nl.po create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/modules/l10n_update_test_translate/translations/l10n_update_test_translate.xx.po create mode 100644 sites/all/modules/contrib/localisation/l10n_update/tests/test.de.po create mode 100644 sites/all/modules/contrib/migrate/migrate/migrate_example/xml/0002.xml create mode 100644 sites/all/modules/contrib/migrate/migrate/migrate_example/xml/index2.xml create mode 100644 sites/all/modules/contrib/migrate/migrate/migrate_example/xml/producers3.xml create mode 100644 sites/all/modules/contrib/migrate/migrate/migrate_example/xml/producers4.xml create mode 100644 sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.install create mode 100644 sites/all/modules/contrib/migrate/migrate/migrate_ui/migrate_ui.wizard.inc create mode 100644 sites/all/modules/contrib/migrate/migrate/plugins/destinations/block_custom.inc create mode 100644 sites/all/modules/contrib/migrate/migrate/plugins/destinations/variable.inc create mode 100644 sites/all/modules/contrib/migrate/migrate/plugins/sources/db2.inc create mode 100644 sites/all/modules/contrib/migrate/migrate/plugins/sources/mongodb.inc create mode 100644 sites/all/modules/contrib/migrate/migrate/plugins/sources/spreadsheet.inc create mode 100644 sites/all/modules/contrib/panels/panels/i18n_panels/README.txt create mode 100644 sites/all/modules/contrib/panels/panels/i18n_panels/i18n_panels.i18n.inc create mode 100644 sites/all/modules/contrib/panels/panels/i18n_panels/i18n_panels.info create mode 100644 sites/all/modules/contrib/panels/panels/i18n_panels/i18n_panels.install create mode 100644 sites/all/modules/contrib/panels/panels/i18n_panels/i18n_panels.module delete mode 100644 sites/all/modules/contrib/panels/panels/js/panels.js create mode 100644 sites/all/modules/contrib/panels/panels/panels.api.php create mode 100644 sites/all/modules/contrib/panels/panels/panels_ipe/css/panels_ipe-rtl.css create mode 100644 sites/all/modules/contrib/panels/panels/panels_mini/panels_mini.css create mode 100644 sites/all/modules/contrib/panels/panels/plugins/cache/simple.inc create mode 100644 sites/all/modules/contrib/seo/metatag/metatag.drush.inc create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_devel/metatag_devel.info create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_devel/metatag_devel.module create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_facebook/metatag_facebook.info create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_facebook/metatag_facebook.install create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_facebook/metatag_facebook.metatag.inc create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_facebook/metatag_facebook.module create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_google_plus/README.txt create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_google_plus/metatag_google_plus.inc create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_google_plus/metatag_google_plus.info create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_google_plus/metatag_google_plus.metatag.inc create mode 100644 sites/all/modules/contrib/seo/metatag/metatag_google_plus/metatag_google_plus.module delete mode 100644 sites/all/modules/contrib/seo/metatag/metatag_ui/metatag_ui.info delete mode 100644 sites/all/modules/contrib/seo/metatag/metatag_ui/metatag_ui.module create mode 100644 sites/all/modules/contrib/theming/extlink/extlink_s.png create mode 100644 sites/all/modules/contrib/users/faq/.gitignore diff --git a/sites/all/modules/contrib/admin/date_popup_authored/.travis.yml b/sites/all/modules/contrib/admin/date_popup_authored/.travis.yml new file mode 100644 index 00000000..fe458850 --- /dev/null +++ b/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 diff --git a/sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.markdown b/sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.markdown new file mode 100644 index 00000000..3573910a --- /dev/null +++ b/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 diff --git a/sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.txt b/sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.txt deleted file mode 100644 index 7d777e14..00000000 --- a/sites/all/modules/contrib/admin/date_popup_authored/CHANGELOG.txt +++ /dev/null @@ -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. diff --git a/sites/all/modules/contrib/admin/date_popup_authored/README.markdown b/sites/all/modules/contrib/admin/date_popup_authored/README.markdown new file mode 100644 index 00000000..badd0e78 --- /dev/null +++ b/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" \ No newline at end of file diff --git a/sites/all/modules/contrib/admin/date_popup_authored/README.txt b/sites/all/modules/contrib/admin/date_popup_authored/README.txt deleted file mode 100644 index 320bf89b..00000000 --- a/sites/all/modules/contrib/admin/date_popup_authored/README.txt +++ /dev/null @@ -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. diff --git a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.info b/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.info index bb7cfc74..6f8bb86f 100644 --- a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.install b/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.install new file mode 100644 index 00000000..8e4095e4 --- /dev/null +++ b/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.install @@ -0,0 +1,17 @@ +type); + variable_del('date_popup_authored_format_' . $node_type->type); + variable_del('date_popup_authored_year_range_' . $node_type->type); + } +} diff --git a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.module b/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.module index a761adc6..9bf1038b 100644 --- a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.module +++ b/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) { +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; + } - // 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; - } + $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')); - // 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'); + // Unset options that are not relevant to date_popup + unset($form['author']['date']['#maxlength']); + unset($form['author']['date']['#description']); - // 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'; + // We need to modify date popup's data during submit + // @see http://drupal.org/node/847854 + $form['#submit'][] = 'date_popup_authored_node_form_submit'; +} + +/** + * 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; +} + +/** + * 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; + } + + // 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; } } diff --git a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.test b/sites/all/modules/contrib/admin/date_popup_authored/tests/date_popup_authored_format.test similarity index 60% rename from sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.test rename to sites/all/modules/contrib/admin/date_popup_authored/tests/date_popup_authored_format.test index d735f331..28958e49 100644 --- a/sites/all/modules/contrib/admin/date_popup_authored/date_popup_authored.test +++ b/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')); + } } diff --git a/sites/all/modules/contrib/admin/features/features.admin.inc b/sites/all/modules/contrib/admin/features/features.admin.inc index 4e12576f..a4203a02 100644 --- a/sites/all/modules/contrib/admin/features/features.admin.inc +++ b/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') . '
' . t('May only contain lowercase letters, numbers and underscores. Try to avoid conflicts with the names of existing Drupal projects.'), '#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 = ''; + 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. */ diff --git a/sites/all/modules/contrib/admin/features/features.api.php b/sites/all/modules/contrib/admin/features/features.api.php index 10915e16..f5984e0c 100644 --- a/sites/all/modules/contrib/admin/features/features.api.php +++ b/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. diff --git a/sites/all/modules/contrib/admin/features/features.css b/sites/all/modules/contrib/admin/features/features.css index 16c7f723..e3f0251d 100644 --- a/sites/all/modules/contrib/admin/features/features.css +++ b/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; } \ No newline at end of file diff --git a/sites/all/modules/contrib/admin/features/features.drush.inc b/sites/all/modules/contrib/admin/features/features.drush.inc index 9da547dd..d9f3a2ec 100644 --- a/sites/all/modules/contrib/admin/features/features.drush.inc +++ b/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(). * diff --git a/sites/all/modules/contrib/admin/features/features.export.inc b/sites/all/modules/contrib/admin/features/features.export.inc index 2f32c95c..154b26bc 100644 --- a/sites/all/modules/contrib/admin/features/features.export.inc +++ b/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); diff --git a/sites/all/modules/contrib/admin/features/features.info b/sites/all/modules/contrib/admin/features/features.info index f563a4b1..205f2c8f 100644 --- a/sites/all/modules/contrib/admin/features/features.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/features/features.install b/sites/all/modules/contrib/admin/features/features.install index 762a054f..9b1fd901 100644 --- a/sites/all/modules/contrib/admin/features/features.install +++ b/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. * diff --git a/sites/all/modules/contrib/admin/features/features.js b/sites/all/modules/contrib/admin/features/features.js index 877285cc..023247a4 100644 --- a/sites/all/modules/contrib/admin/features/features.js +++ b/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('  '); - 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($(''+ Drupal.t('Edit') +'').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'); diff --git a/sites/all/modules/contrib/admin/features/features.module b/sites/all/modules/contrib/admin/features/features.module index ba5b9fe5..3743963b 100644 --- a/sites/all/modules/contrib/admin/features/features.module +++ b/sites/all/modules/contrib/admin/features/features.module @@ -167,6 +167,17 @@ function features_menu() { 'file' => 'features.admin.inc', ); } + + $items['admin/structure/features/%feature/lock'] = array( + 'title' => 'Lock', + 'description' => 'Lock a feature or components.', + 'page callback' => 'features_admin_lock', + 'page arguments' => array(3, 5, 6), + 'load arguments' => array(3, TRUE, TRUE), + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); $items['admin/structure/features/%feature/status'] = array( 'title' => 'Status', 'description' => 'Javascript status call back.', @@ -214,11 +225,11 @@ function features_theme() { $items = array(); $items['features_module_status'] = array( - 'variables' => array('module' => null, 'status' => null) + 'variables' => array('module' => NULL, 'status' => NULL) ) + $base; $items['features_components'] = array( - 'variables' => array('info' => null, 'sources' => null), + 'variables' => array('info' => NULL, 'sources' => NULL), ) + $base; $items['features_component_key'] = $base; @@ -227,7 +238,11 @@ function features_theme() { ) + $base; $items['features_storage_link'] = array( - 'variables' => array('storage' => null, 'text' => null, 'path' => null, 'options' => array()), + 'variables' => array('storage' => NULL, 'text' => NULL, 'path' => NULL, 'options' => array()), + ) + $base; + + $items['features_lock_link'] = array( + 'variables' => array('feature' => NULL, 'component' => NULL, 'locked' => FALSE), ) + $base; $items['features_form_components'] = @@ -290,6 +305,11 @@ function features_permission() { 'description' => t('Allow feature exports to be generated and written directly to site.'), 'restrict access' => TRUE, ), + 'rename features' => array( + 'title' => t('Edit feature machine name'), + 'description' => t('Allows editing machine name of a disabled feature'), + 'restrict access' => TRUE, + ), ); } @@ -299,10 +319,10 @@ function features_permission() { function features_help($path, $arg) { switch ($path) { case 'admin/help#features': - $output = file_get_contents(drupal_get_path('module', 'features') .'/README.txt'); - return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '
'. check_plain($output) .'
'; + $output = file_get_contents(drupal_get_path('module', 'features') . '/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '
' . check_plain($output) . '
'; case 'admin/build/features': - return '

'. t('A "Feature" is a certain type of Drupal module which contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') .'

'; + return '

' . t('A "Feature" is a certain type of Drupal module which contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') . '

'; } } @@ -641,6 +661,10 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) { $files = system_rebuild_module_data(); foreach ($files as $row) { + // Remove modification timestamp, added in Drupal 7.33. + if (isset($row->info['mtime'])) { + unset($row->info['mtime']); + } // Avoid false-reported feature overrides for php = 5.2.4 line in .info file. if (isset($row->info['php'])) { unset($row->info['php']); @@ -654,7 +678,7 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) { if (!empty($row->info['features'])) { // Fix css/js paths if (!empty($row->info['stylesheets'])) { - foreach($row->info['stylesheets'] as $media => $css) { + foreach ($row->info['stylesheets'] as $media => $css) { $row->info['stylesheets'][$media] = array_keys($css); } } @@ -789,7 +813,7 @@ function features_get_conflicts($reset = FALSE) { if (isset($component_info[$type]['duplicates']) && $component_info[$type]['duplicates'] == FEATURES_DUPLICATES_ALLOWED) { continue; } - else if (count($modules) > 1) { + elseif (count($modules) > 1) { foreach ($modules as $module) { if (!isset($conflicts[$module])) { $conflicts[$module] = array(); @@ -840,7 +864,7 @@ function features_get_module_status($module) { if (module_exists($module)) { return FEATURES_MODULE_ENABLED; } - else if (features_get_modules($module)) { + elseif (features_get_modules($module)) { return FEATURES_MODULE_DISABLED; } else { @@ -886,6 +910,7 @@ function features_form_system_modules_alter(&$form) { * Restore the specified modules to the default state. */ function _features_restore($op, $items = array()) { + $lockable = FALSE; // Set this variable in $conf if having timeout issues during install/rebuild. if (variable_get('features_restore_time_limit_' . $op, FALSE) !== FALSE) { drupal_set_time_limit(variable_get('features_restore_time_limit_' . $op, FALSE)); @@ -899,11 +924,13 @@ function _features_restore($op, $items = array()) { $restore_states = array(FEATURES_OVERRIDDEN, FEATURES_REBUILDABLE, FEATURES_NEEDS_REVIEW); $restore_hook = 'features_revert'; $log_action = 'Revert'; + $lockable = TRUE; break; case 'rebuild': $restore_states = array(FEATURES_REBUILDABLE); $restore_hook = 'features_rebuild'; $log_action = 'Rebuild'; + $lockable = variable_get('features_lock_mode', 'all') == 'all'; break; case 'disable': $restore_hook = 'features_disable_feature'; @@ -932,8 +959,20 @@ function _features_restore($op, $items = array()) { } } + // Invoke global pre restore hook. + module_invoke_all('features_pre_restore', $op, $items); foreach ($items as $module_name => $components) { + // If feature is totally locked, do not execute past this stage. + if ($lockable && features_feature_is_locked($module_name)) { + watchdog('features', 'Tried @actioning a locked @module_name, aborted.', array('@action' => $log_action, '@module_name' => $module_name)); + continue; + } foreach ($components as $component) { + // If feature is totally locked, do not execute past this stage. + if ($lockable && features_feature_is_locked($module_name, $component)) { + watchdog('features', 'Tried @actioning a locked @module_name / @component, aborted.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); + continue; + } // Invoke pre hook $pre_hook = 'pre_' . $restore_hook; module_invoke($module_name, $pre_hook, $component); @@ -955,6 +994,8 @@ function _features_restore($op, $items = array()) { module_invoke($module_name, $post_hook, $component); } } + // Invoke global post restore hook. + module_invoke_all('features_post_restore', $op, $items); } /** @@ -1034,6 +1075,9 @@ function features_hook_info() { * Change vocabularies permission, from vocab id to machine name and vice versa. */ function _user_features_change_term_permission(&$perm, $type = 'vid') { + if (!module_exists('taxonomy')) { + return; + } // Export vocabulary permissions using the machine name, instead of vocabulary // id. if (strpos($perm, 'edit terms in ') !== FALSE || strpos($perm, 'delete terms in ') !== FALSE) { @@ -1109,3 +1153,53 @@ function features_get_deprecated($components = array()) { } return $deprecated; } + +/** + * Returns whether a feature or it's component is locked. + */ +function features_feature_is_locked($feature, $component = NULL, $check_global_component_setting = TRUE) { + $locked = variable_get('features_feature_locked', array()); + if ($component) { + return ($check_global_component_setting && features_component_is_locked($component)) || !empty($locked[$feature][$component]); + } + else { + return !empty($locked[$feature]['_all']); + } +} + +/** + * Returns whether a component is locked. + */ +function features_component_is_locked($component) { + return variable_get('features_component_locked_' . $component, FALSE); +} + +/** + * Locks a feature or it's component. + */ +function features_feature_lock($feature, $component = NULL) { + $locked = variable_get('features_feature_locked', array()); + $locked[$feature] = !empty($locked[$feature]) ? $locked[$feature] : array(); + if ($component) { + $locked[$feature][$component] = TRUE; + } + else { + $locked[$feature]['_all'] = TRUE; + } + variable_set('features_feature_locked', $locked); +} + + +/** + * Unlocks a feature or it's component. + */ +function features_feature_unlock($feature, $component = NULL) { + $locked = variable_get('features_feature_locked', array()); + if ($component) { + unset($locked[$feature][$component]); + } + else { + unset($locked[$feature]['_all']); + } + variable_set('features_feature_locked', $locked); +} diff --git a/sites/all/modules/contrib/admin/features/includes/features.field.inc b/sites/all/modules/contrib/admin/features/includes/features.field.inc index 09104a32..73332271 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.field.inc +++ b/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); +} diff --git a/sites/all/modules/contrib/admin/features/includes/features.image.inc b/sites/all/modules/contrib/admin/features/includes/features.image.inc index 2b5eb273..b2058b7c 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.image.inc +++ b/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', + ))); } } diff --git a/sites/all/modules/contrib/admin/features/includes/features.locale.inc b/sites/all/modules/contrib/admin/features/includes/features.locale.inc index fc27174b..126d177d 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.locale.inc +++ b/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(); diff --git a/sites/all/modules/contrib/admin/features/includes/features.menu.inc b/sites/all/modules/contrib/admin/features/includes/features.menu.inc index a11350ff..c883e6ac 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.menu.inc +++ b/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; } } diff --git a/sites/all/modules/contrib/admin/features/includes/features.node.inc b/sites/all/modules/contrib/admin/features/includes/features.node.inc index 7beb55fb..f40341af 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.node.inc +++ b/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); } } diff --git a/sites/all/modules/contrib/admin/features/includes/features.user.inc b/sites/all/modules/contrib/admin/features/includes/features.user.inc index 2805a774..152c5a82 100644 --- a/sites/all/modules/contrib/admin/features/includes/features.user.inc +++ b/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 diff --git a/sites/all/modules/contrib/admin/features/tests/features_test/features_test.features.inc b/sites/all/modules/contrib/admin/features/tests/features_test/features_test.features.inc index 38aaa4d4..8d0646ff 100644 --- a/sites/all/modules/contrib/admin/features/tests/features_test/features_test.features.inc +++ b/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, diff --git a/sites/all/modules/contrib/admin/features/tests/features_test/features_test.info b/sites/all/modules/contrib/admin/features/tests/features_test/features_test.info index 382e1505..d27d18ce 100644 --- a/sites/all/modules/contrib/admin/features/tests/features_test/features_test.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/features/theme/features-admin-components.tpl.php b/sites/all/modules/contrib/admin/features/theme/features-admin-components.tpl.php index 69faadfb..04213a0f 100644 --- a/sites/all/modules/contrib/admin/features/theme/features-admin-components.tpl.php +++ b/sites/all/modules/contrib/admin/features/theme/features-admin-components.tpl.php @@ -3,7 +3,7 @@
-

+

diff --git a/sites/all/modules/contrib/admin/features/theme/theme.inc b/sites/all/modules/contrib/admin/features/theme/theme.inc index b5e90cfb..e0f3701d 100644 --- a/sites/all/modules/contrib/admin/features/theme/theme.inc +++ b/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 "$text"; } +/** + * 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. */ diff --git a/sites/all/modules/contrib/admin/filter_perms/filter_perms.info b/sites/all/modules/contrib/admin/filter_perms/filter_perms.info index 9962fd0b..e6d850bd 100644 --- a/sites/all/modules/contrib/admin/filter_perms/filter_perms.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/google_analytics/README.txt b/sites/all/modules/contrib/admin/google_analytics/README.txt index 1628cf78..fab2f2ec 100644 --- a/sites/all/modules/contrib/admin/google_analytics/README.txt +++ b/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. -Slot: 1 -Name: User roles -Value: [current-user:role-names] -Scope: Visitor +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. -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 +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. + + Index: 1 + Value: [current-user:role-names] + +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. diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc index 40d5cadf..4449077d 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.inc @@ -80,7 +80,15 @@ function googleanalytics_admin_settings_form($form_state) { '#title' => t('List of top-level domains'), '#type' => 'textarea', '#default_value' => variable_get('googleanalytics_cross_domains', ''), - '#description' => t('If you selected "Multiple top-level domains" above, enter all related top-level domains. Add one domain per line. By default, the data in your reports only includes the path and name of the page, and not the domain name. For more information see section Show separate domain names in Tracking Multiple Domains.', array('@url' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '1034342'))))), + '#description' => t('If you selected "Multiple top-level domains" above, enter all related top-level domains. Add one domain per line. By default, the data in your reports only includes the path and name of the page, and not the domain name. For more information see section Show separate domain names in Tracking Multiple Domains.', array('@url' => 'https://support.google.com/analytics/answer/1034342')), + '#states' => array( + 'enabled' => array( + ':input[name="googleanalytics_domain_mode"]' => array('value' => '2'), + ), + 'required' => array( + ':input[name="googleanalytics_domain_mode"]' => array('value' => '2'), + ), + ), ); // Page specific visibility configurations. @@ -97,8 +105,8 @@ function googleanalytics_admin_settings_form($form_state) { if ($visibility == 2 && !$php_access) { $form['tracking']['page_vis_settings'] = array(); - $form['tracking']['page_vis_settings']['visibility'] = array('#type' => 'value', '#value' => 2); - $form['tracking']['page_vis_settings']['pages'] = array('#type' => 'value', '#value' => $pages); + $form['tracking']['page_vis_settings']['googleanalytics_visibility_pages'] = array('#type' => 'value', '#value' => 2); + $form['tracking']['page_vis_settings']['googleanalytics_pages'] = array('#type' => 'value', '#value' => $pages); } else { $options = array( @@ -172,6 +180,12 @@ function googleanalytics_admin_settings_form($form_state) { ), '#default_value' => variable_get('googleanalytics_custom', 0), ); + $form['tracking']['user_vis_settings']['googleanalytics_trackuserid'] = array( + '#type' => 'checkbox', + '#title' => t('Track User ID'), + '#default_value' => variable_get('googleanalytics_trackuserid', 0), + '#description' => t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. Learn more about the benefits of using User ID.', array('@url' => 'https://support.google.com/analytics/answer/3123663')), + ); // Link specific configurations. $form['tracking']['linktracking'] = array( @@ -199,7 +213,28 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'textfield', '#default_value' => variable_get('googleanalytics_trackfiles_extensions', GOOGLEANALYTICS_TRACKFILES_EXTENSIONS), '#description' => t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: !extensions', array('!extensions' => GOOGLEANALYTICS_TRACKFILES_EXTENSIONS)), - '#maxlength' => 255, + '#maxlength' => 500, + '#states' => array( + 'enabled' => array( + ':input[name="googleanalytics_trackfiles"]' => array('checked' => TRUE), + ), + // Note: Form required marker is not visible as title is invisible. + 'required' => array( + ':input[name="googleanalytics_trackfiles"]' => array('checked' => TRUE), + ), + ), + ); + $form['tracking']['linktracking']['googleanalytics_tracklinkid'] = array( + '#type' => 'checkbox', + '#title' => t('Track enhanced link attribution'), + '#default_value' => variable_get('googleanalytics_tracklinkid', 0), + '#description' => t('Enhanced Link Attribution improves the accuracy of your In-Page Analytics report by automatically differentiating between multiple links to the same URL on a single page by using link element IDs. Enable enhanced link attribution in the Admin UI of your Google Analytics account.', array('@url' => 'https://support.google.com/analytics/answer/2558867')), + ); + $form['tracking']['linktracking']['googleanalytics_trackurlfragments'] = array( + '#type' => 'checkbox', + '#title' => t('Track changing URL fragments as pageviews'), + '#default_value' => variable_get('googleanalytics_trackurlfragments', 0), + '#description' => t('By default, the URL reported to Google Analytics will not include the "fragment identifier" (i.e. the portion of the URL beginning with a hash sign), and hash changes by themselves will not cause new pageviews to be reported. Checking this box will cause hash changes to be reported as pageviews (in modern browsers) and all pageview URLs to include the fragment where applicable.'), ); // Message specific configurations. @@ -211,7 +246,7 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'checkboxes', '#title' => t('Track messages of type'), '#default_value' => variable_get('googleanalytics_trackmessages', array()), - '#description' => t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. Keep in mind that Google allows a maximum of 500 events per session only and every message is tracked as one individual event. If the limit has been exceeded no further events are tracked, but normal page tracking is not effected. Messages from excluded pages cannot tracked.'), + '#description' => t('This will track the selected message types shown to users. Tracking of form validation errors may help you identifying usability issues in your site. For each visit (user session), a maximum of approximately 500 combined GATC requests (both events and page views) can be tracked. Every message is tracked as one individual event. Note that - as the number of events in a session approaches the limit - additional events might not be tracked. Messages from excluded pages cannot tracked.'), '#options' => array( 'status' => t('Status message'), 'warning' => t('Warning message'), @@ -219,21 +254,19 @@ function googleanalytics_admin_settings_form($form_state) { ), ); - // Google already have many translations, if not - they display a note to change the language. - global $language; $form['tracking']['search_and_advertising'] = array( '#type' => 'fieldset', '#title' => t('Search and Advertising'), ); - $site_search_dependencies = '
'; - $site_search_dependencies .= t('Depends on: !dependencies', array('!dependencies' => (module_exists('search') ? t('@module (enabled)', array('@module' => 'Search')) : t('@module (disabled)', array('@module' => 'Search'))))); + $site_search_dependencies = '
'; + $site_search_dependencies .= t('Requires: !module-list', array('!module-list' => (module_exists('search') ? t('@module (enabled)', array('@module' => 'Search')) : t('@module (disabled)', array('@module' => 'Search'))))); $site_search_dependencies .= '
'; $form['tracking']['search_and_advertising']['googleanalytics_site_search'] = array( '#type' => 'checkbox', '#title' => t('Track internal search'), - '#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter search. For more information see Setting Up Site Search for a Profile.', array('@url' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '1012264'))))) . $site_search_dependencies, + '#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter search. For more information see Setting Up Site Search for a Profile.', array('@url' => 'https://support.google.com/analytics/answer/1012264')) . $site_search_dependencies, '#default_value' => variable_get('googleanalytics_site_search', FALSE), '#disabled' => (module_exists('search') ? FALSE : TRUE), ); @@ -245,8 +278,8 @@ function googleanalytics_admin_settings_form($form_state) { ); $form['tracking']['search_and_advertising']['googleanalytics_trackdoubleclick'] = array( '#type' => 'checkbox', - '#title' => t('Track DoubleClick data'), - '#description' => t('If checked, the alternative Google DoubleClick data tracking is used to enable AdWords remarketing features. If you choose this option you will need to update your privacy policy.', array('@doubleclick' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '2444872'))), '@privacy' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '2636405'))))), + '#title' => t('Track display features'), + '#description' => t('The display features plugin can be used to enable Display Advertising Features in Google Analytics, such as Remarketing, Demographics and Interest Reporting, and more. Learn more about Display Advertising Features in Google Analytics. If you choose this option you will need to update your privacy policy.', array('@displayfeatures' => 'https://support.google.com/analytics/answer/3450482', '@privacy' => 'https://support.google.com/analytics/answer/2700409')), '#default_value' => variable_get('googleanalytics_trackdoubleclick', FALSE), ); @@ -259,7 +292,7 @@ function googleanalytics_admin_settings_form($form_state) { '#type' => 'checkbox', '#title' => t('Anonymize visitors IP address'), '#description' => t('Tell Google Analytics to anonymize the information sent by the tracker objects by removing the last octet of the IP address prior to its storage. Note that this will slightly reduce the accuracy of geographic reporting. In some countries it is not allowed to collect personally identifying information for privacy reasons and this setting may help you to comply with the local laws.'), - '#default_value' => variable_get('googleanalytics_tracker_anonymizeip', 0), + '#default_value' => variable_get('googleanalytics_tracker_anonymizeip', 1), ); $form['tracking']['privacy']['googleanalytics_privacy_donottrack'] = array( '#type' => 'checkbox', @@ -268,79 +301,107 @@ function googleanalytics_admin_settings_form($form_state) { '#default_value' => variable_get('googleanalytics_privacy_donottrack', 1), ); - // Custom variables. - $form['googleanalytics_custom_var'] = array( + // Custom Dimensions. + $form['googleanalytics_custom_dimension'] = array( '#collapsed' => TRUE, '#collapsible' => TRUE, - '#description' => t('You can add Google Analytics Custom Variables here. These will be added to every page that Google Analytics tracking code appears on. Google Analytics will only accept custom variables if the name and value combined are less than 128 bytes after URL encoding. Keep the names as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables')), + '#description' => t('You can set values for Google Analytics Custom Dimensions here. You must have already configured your custom dimensions in the Google Analytics Management Interface. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available. A dimension value is allowed to have a maximum lenght of 150 bytes. Expect longer values to get trimmed.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')), '#theme' => 'googleanalytics_admin_custom_var_table', - '#title' => t('Custom variables'), + '#title' => t('Custom dimensions'), '#tree' => TRUE, '#type' => 'fieldset', ); - $googleanalytics_custom_vars = variable_get('googleanalytics_custom_var', array()); + $googleanalytics_custom_dimension = variable_get('googleanalytics_custom_dimension', array()); - // Google Analytics supports up to 5 custom variables. - for ($i = 1; $i < 6; $i++) { - $form['googleanalytics_custom_var']['slots'][$i]['slot'] = array( + // Google Analytics supports up to 20 custom dimensions. + for ($i = 1; $i <= 20; $i++) { + $form['googleanalytics_custom_dimension']['indexes'][$i]['index'] = array( '#default_value' => $i, - '#description' => t('Slot number'), + '#description' => t('Index number'), '#disabled' => TRUE, '#size' => 1, - '#title' => t('Custom variable slot #@slot', array('@slot' => $i)), + '#title' => t('Custom dimension index #@index', array('@index' => $i)), '#title_display' => 'invisible', '#type' => 'textfield', ); - $form['googleanalytics_custom_var']['slots'][$i]['name'] = array( - '#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['name']) ? $googleanalytics_custom_vars['slots'][$i]['name'] : '', - '#description' => t('The custom variable name.'), + $form['googleanalytics_custom_dimension']['indexes'][$i]['value'] = array( + '#default_value' => isset($googleanalytics_custom_dimension[$i]['value']) ? $googleanalytics_custom_dimension[$i]['value'] : '', + '#description' => t('The custom dimension value.'), '#maxlength' => 255, - '#size' => 20, - '#title' => t('Custom variable name #@slot', array('@slot' => $i)), - '#title_display' => 'invisible', - '#type' => 'textfield', - '#element_validate' => array('googleanalytics_token_element_validate'), - '#token_types' => array('node'), - ); - $form['googleanalytics_custom_var']['slots'][$i]['value'] = array( - '#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['value']) ? $googleanalytics_custom_vars['slots'][$i]['value'] : '', - '#description' => t('The custom variable value.'), - '#maxlength' => 255, - '#title' => t('Custom variable value #@slot', array('@slot' => $i)), + '#title' => t('Custom dimension value #@index', array('@index' => $i)), '#title_display' => 'invisible', '#type' => 'textfield', '#element_validate' => array('googleanalytics_token_element_validate'), '#token_types' => array('node'), ); if (module_exists('token')) { - $form['googleanalytics_custom_var']['slots'][$i]['name']['#element_validate'][] = 'token_element_validate'; - $form['googleanalytics_custom_var']['slots'][$i]['value']['#element_validate'][] = 'token_element_validate'; + $form['googleanalytics_custom_dimension']['indexes'][$i]['value']['#element_validate'][] = 'token_element_validate'; } - $form['googleanalytics_custom_var']['slots'][$i]['scope'] = array( - '#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['scope']) ? $googleanalytics_custom_vars['slots'][$i]['scope'] : 3, - '#description' => t('The scope for the custom variable.'), - '#title' => t('Custom variable slot #@slot', array('@slot' => $i)), - '#title_display' => 'invisible', - '#type' => 'select', - '#options' => array( - 1 => t('Visitor'), - 2 => t('Session'), - 3 => t('Page'), - ), + } + + $form['googleanalytics_custom_dimension']['googleanalytics_description'] = array( + '#type' => 'item', + '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom dimensions. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')), + ); + if (module_exists('token')) { + $form['googleanalytics_custom_dimension']['googleanalytics_token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array('node'), + '#dialog' => TRUE, ); } - $form['googleanalytics_custom_var']['googleanalytics_custom_var_description'] = array( - '#type' => 'item', - '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom variables. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')), - ); - $form['googleanalytics_custom_var']['googleanalytics_custom_var_token_tree'] = array( - '#theme' => 'token_tree', - '#token_types' => array('node'), - '#dialog' => TRUE, + // Custom Metrics. + $form['googleanalytics_custom_metric'] = array( + '#collapsed' => TRUE, + '#collapsible' => TRUE, + '#description' => t('You can add Google Analytics Custom Metrics here. You must have already configured your custom metrics in the Google Analytics Management Interface. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')), + '#theme' => 'googleanalytics_admin_custom_var_table', + '#title' => t('Custom metrics'), + '#tree' => TRUE, + '#type' => 'fieldset', ); + $googleanalytics_custom_metric = variable_get('googleanalytics_custom_metric', array()); + + // Google Analytics supports up to 20 custom metrics. + for ($i = 1; $i <= 20; $i++) { + $form['googleanalytics_custom_metric']['indexes'][$i]['index'] = array( + '#default_value' => $i, + '#description' => t('Index number'), + '#disabled' => TRUE, + '#size' => 1, + '#title' => t('Custom metric index #@index', array('@index' => $i)), + '#title_display' => 'invisible', + '#type' => 'textfield', + ); + $form['googleanalytics_custom_metric']['indexes'][$i]['value'] = array( + '#default_value' => isset($googleanalytics_custom_metric[$i]['value']) ? $googleanalytics_custom_metric[$i]['value'] : '', + '#description' => t('The custom metric value.'), + '#maxlength' => 255, + '#title' => t('Custom metric value #@index', array('@index' => $i)), + '#title_display' => 'invisible', + '#type' => 'textfield', + '#element_validate' => array('googleanalytics_token_element_validate'), + '#token_types' => array('node'), + ); + if (module_exists('token')) { + $form['googleanalytics_custom_metric']['indexes'][$i]['value']['#element_validate'][] = 'token_element_validate'; + } + } + + $form['googleanalytics_custom_metric']['googleanalytics_description'] = array( + '#type' => 'item', + '#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom metrics. Section 7 of the Google Analytics terms of service requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')), + ); + if (module_exists('token')) { + $form['googleanalytics_custom_metric']['googleanalytics_token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array('node'), + '#dialog' => TRUE, + ); + } // Advanced feature configurations. $form['advanced'] = array( @@ -372,33 +433,36 @@ function googleanalytics_admin_settings_form($form_state) { '#title' => t('Custom JavaScript code'), '#collapsible' => TRUE, '#collapsed' => TRUE, - '#description' => t('You can add custom Google Analytics code snippets here. These will be added every time tracking is in effect. Before you add your custom code, you should read the Google Analytics Tracking Code - Functional Overview and the Google Analytics Tracking API documentation. Do not include the <script> tags, and always end your code with a semicolon (;).', array('@snippets' => 'http://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/gajs/methods/')), + '#description' => t('You can add custom Google Analytics code snippets here. These will be added every time tracking is in effect. Before you add your custom code, you should read the Google Analytics Tracking Code - Functional Overview and the Google Analytics Tracking API documentation. Do not include the <script> tags, and always end your code with a semicolon (;).', array('@snippets' => 'http://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference')), + ); + $form['advanced']['codesnippet']['googleanalytics_codesnippet_create'] = array( + '#type' => 'textarea', + '#title' => t('Create only fields'), + '#default_value' => _googleanalytics_get_name_value_string(variable_get('googleanalytics_codesnippet_create', array())), + '#rows' => 5, + '#description' => t('Enter one value per line, in the format name|value. Settings in this textarea will be added to ga("create", "UA-XXXX-Y", {"name":"value"});. For more information, read create only fields documentation in the Analytics.js field reference.', array('@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create')), + '#element_validate' => array('googleanalytics_validate_create_field_values'), ); $form['advanced']['codesnippet']['googleanalytics_codesnippet_before'] = array( '#type' => 'textarea', '#title' => t('Code snippet (before)'), '#default_value' => variable_get('googleanalytics_codesnippet_before', ''), '#rows' => 5, - '#description' => t("Code in this textarea will be added before _gaq.push(['_trackPageview'])."), + '#description' => t('Code in this textarea will be added before ga("send", "pageview");.'), ); $form['advanced']['codesnippet']['googleanalytics_codesnippet_after'] = array( '#type' => 'textarea', '#title' => t('Code snippet (after)'), '#default_value' => variable_get('googleanalytics_codesnippet_after', ''), '#rows' => 5, - '#description' => t("Code in this textarea will be added after _gaq.push(['_trackPageview']). This is useful if you'd like to track a site in two accounts."), + '#description' => t('Code in this textarea will be added after ga("send", "pageview");. This is useful if you\'d like to track a site in two accounts.'), ); - $form['advanced']['googleanalytics_js_scope'] = array( - '#type' => 'select', - '#title' => t('JavaScript scope'), - '#description' => t('Google recommends adding the external JavaScript files to the header for performance reasons. If Multiple top-level domains has been selected, this setting will be forced to header.'), - '#options' => array( - 'footer' => t('Footer'), - 'header' => t('Header'), - ), - '#default_value' => variable_get('googleanalytics_js_scope', 'header'), - '#disabled' => (variable_get('googleanalytics_domain_mode', 0) == 2) ? TRUE : FALSE, + $form['advanced']['googleanalytics_debug'] = array( + '#type' => 'checkbox', + '#title' => t('Enable debugging'), + '#description' => t('If checked, the Google Universal Analytics debugging script will be loaded. You should not enable your production site to use this version of the JavaScript. The analytics_debug.js script is larger than the analytics.js tracking code and it is not typically cached. Using it in your production site will slow down your site for all of your users. Again, this is only for your own testing purposes. Debug messages are printed to the window.console object.'), + '#default_value' => variable_get('googleanalytics_debug', 0), ); return system_settings_form($form); @@ -408,31 +472,37 @@ function googleanalytics_admin_settings_form($form_state) { * Implements _form_validate(). */ function googleanalytics_admin_settings_form_validate($form, &$form_state) { - // Custom variables validation. - foreach ($form_state['values']['googleanalytics_custom_var']['slots'] as $custom_var) { - $form_state['values']['googleanalytics_custom_var']['slots'][$custom_var['slot']]['name'] = trim($custom_var['name']); - $form_state['values']['googleanalytics_custom_var']['slots'][$custom_var['slot']]['value'] = trim($custom_var['value']); - - // Validate empty names/values. - if (empty($custom_var['name']) && !empty($custom_var['value'])) { - form_set_error("googleanalytics_custom_var][slots][" . $custom_var['slot'] . "][name", t('The custom variable @slot-number requires a Name if a Value has been provided.', array('@slot-number' => $custom_var['slot']))); - } - elseif (!empty($custom_var['name']) && empty($custom_var['value'])) { - form_set_error("googleanalytics_custom_var][slots][" . $custom_var['slot'] . "][value", t('The custom variable @slot-number requires a Value if a Name has been provided.', array('@slot-number' => $custom_var['slot']))); + // Trim custom dimensions and metrics. + foreach ($form_state['values']['googleanalytics_custom_dimension']['indexes'] as $dimension) { + $form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'] = trim($dimension['value']); + // Remove empty values from the array. + if (!drupal_strlen($form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'])) { + unset($form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]); } } + $form_state['values']['googleanalytics_custom_dimension'] = $form_state['values']['googleanalytics_custom_dimension']['indexes']; + + foreach ($form_state['values']['googleanalytics_custom_metric']['indexes'] as $metric) { + $form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'] = trim($metric['value']); + // Remove empty values from the array. + if (!drupal_strlen($form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'])) { + unset($form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]); + } + } + $form_state['values']['googleanalytics_custom_metric'] = $form_state['values']['googleanalytics_custom_metric']['indexes']; // Trim some text values. $form_state['values']['googleanalytics_account'] = trim($form_state['values']['googleanalytics_account']); $form_state['values']['googleanalytics_pages'] = trim($form_state['values']['googleanalytics_pages']); $form_state['values']['googleanalytics_cross_domains'] = trim($form_state['values']['googleanalytics_cross_domains']); + $form_state['values']['googleanalytics_codesnippet_create'] = _googleanalytics_extract_create_field_values($form_state['values']['googleanalytics_codesnippet_create']); $form_state['values']['googleanalytics_codesnippet_before'] = trim($form_state['values']['googleanalytics_codesnippet_before']); $form_state['values']['googleanalytics_codesnippet_after'] = trim($form_state['values']['googleanalytics_codesnippet_after']); // Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes. $form_state['values']['googleanalytics_account'] = str_replace(array('–', '—', '−'), '-', $form_state['values']['googleanalytics_account']); - if (!preg_match('/^UA-\d{4,}-\d+$/', $form_state['values']['googleanalytics_account'])) { + if (!preg_match('/^UA-\d+-\d+$/', $form_state['values']['googleanalytics_account'])) { form_set_error('googleanalytics_account', t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.')); } @@ -440,16 +510,25 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) { if ($form_state['values']['googleanalytics_domain_mode'] == 2 && empty($form_state['values']['googleanalytics_cross_domains'])) { form_set_error('googleanalytics_cross_domains', t('A list of top-level domains is required if Multiple top-level domains has been selected.')); } + // Clear the domain list if cross domains are disabled. + if ($form_state['values']['googleanalytics_domain_mode'] != 2) { + $form_state['values']['googleanalytics_cross_domains'] = ''; + } + + // Disallow empty list of download file extensions. + if ($form_state['values']['googleanalytics_trackfiles'] && empty($form_state['values']['googleanalytics_trackfiles_extensions'])) { + form_set_error('googleanalytics_trackfiles_extensions', t('List of download file extensions cannot empty.')); + } // Clear obsolete local cache if cache has been disabled. if (empty($form_state['values']['googleanalytics_cache']) && $form['advanced']['googleanalytics_cache']['#default_value']) { googleanalytics_clear_js_cache(); } // This is for the Newbie's who cannot read a text area description. - if (stristr($form_state['values']['googleanalytics_codesnippet_before'], 'google-analytics.com/ga.js')) { + if (stristr($form_state['values']['googleanalytics_codesnippet_before'], 'google-analytics.com/analytics.js')) { form_set_error('googleanalytics_codesnippet_before', t('Do not add the tracker code provided by Google into the javascript code snippets! This module already builds the tracker code based on your Google Analytics account number and settings.')); } - if (stristr($form_state['values']['googleanalytics_codesnippet_after'], 'google-analytics.com/ga.js')) { + if (stristr($form_state['values']['googleanalytics_codesnippet_after'], 'google-analytics.com/analytics.js')) { form_set_error('googleanalytics_codesnippet_after', t('Do not add the tracker code provided by Google into the javascript code snippets! This module already builds the tracker code based on your Google Analytics account number and settings.')); } if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state['values']['googleanalytics_codesnippet_before'])) { @@ -458,11 +537,6 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) { if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state['values']['googleanalytics_codesnippet_after'])) { form_set_error('googleanalytics_codesnippet_after', t('Do not include the <script> tags in the javascript code snippets.')); } - - // Header section must be forced for multiple top-level domains. - if ($form_state['values']['googleanalytics_domain_mode'] == 2) { - $form_state['values']['googleanalytics_js_scope'] = 'header'; - } } /** @@ -472,27 +546,25 @@ function theme_googleanalytics_admin_custom_var_table($variables) { $form = $variables['form']; $header = array( - array('data' => t('Slot')), - array('data' => t('Name')), + array('data' => t('Index')), array('data' => t('Value')), - array('data' => t('Scope')), ); $rows = array(); - foreach (element_children($form['slots']) as $key => $id) { + foreach (element_children($form['indexes']) as $key => $id) { $rows[] = array( 'data' => array( - drupal_render($form['slots'][$id]['slot']), - drupal_render($form['slots'][$id]['name']), - drupal_render($form['slots'][$id]['value']), - drupal_render($form['slots'][$id]['scope']), + drupal_render($form['indexes'][$id]['index']), + drupal_render($form['indexes'][$id]['value']), ), ); } $output = theme('table', array('header' => $header, 'rows' => $rows)); - $output .= drupal_render($form['googleanalytics_custom_var_description']); - $output .= drupal_render($form['googleanalytics_custom_var_token_tree']); + $output .= drupal_render($form['googleanalytics_description']); + if (isset($form['googleanalytics_token_tree'])) { + $output .= drupal_render($form['googleanalytics_token_tree']); + } return $output; } @@ -599,3 +671,198 @@ function _googleanalytics_contains_forbidden_token($token_string) { return preg_match('/' . implode('|', array_map('preg_quote', $token_blacklist)) . '/i', $token_string); } + +/** + * #element_validate callback for create only fields. + * + * @param $element + * An associative array containing the properties and children of the + * generic form element. + * @param $form_state + * The $form_state array for the form this element belongs to. + * + * @see form_process_pattern() + */ +function googleanalytics_validate_create_field_values(&$element, &$form_state) { + $values = _googleanalytics_extract_create_field_values($element['#value']); + + if (!is_array($values)) { + form_error($element, t('The %element-title field contains invalid input.', array('%element-title' => $element['#title']))); + } + else { + // Check that name and value are valid for the field type. + foreach ($values as $name => $value) { + if ($error = _googleanalytics_validate_create_field_name($name)) { + form_error($element, $error); + break; + } + if ($error = _googleanalytics_validate_create_field_value($value)) { + form_error($element, $error); + break; + } + } + + return $element; + } +} + +/** + * Extracts the values array from the element. + * + * @param string $string + * The raw string to extract values from. + * + * @return array|null + * The array of extracted key/value pairs, or NULL if the string is invalid. + * + * @see \Drupal\options\Plugin\Field\FieldType\ListTextItem::allowedValuesString() + */ +function _googleanalytics_extract_create_field_values($string) { + $values = array(); + + $list = explode("\n", $string); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + + foreach ($list as $position => $text) { + // Check for an explicit key. + $matches = array(); + if (preg_match('/(.*)\|(.*)/', $text, $matches)) { + // Trim key and value to avoid unwanted spaces issues. + $name = trim($matches[1]); + $value = trim($matches[2]); + } + else { + return; + } + + $values[$name] = $value; + } + + return _googleanalytics_convert_form_value_data_types($values); +} + +/** + * Checks whether a field name is valid. + * + * @param string $name + * The option value entered by the user. + * + * @return string + * The error message if the specified value is invalid, NULL otherwise. + */ +function _googleanalytics_validate_create_field_name($name) { + // List of supported field names: + // https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create + $create_only_fields = array( + 'clientId', + 'userId', + 'sampleRate', + 'siteSpeedSampleRate', + 'alwaysSendReferrer', + 'allowAnchor', + 'cookieName', + 'cookieDomain', + 'cookieExpires', + 'legacyCookieDomain', + ); + + if ($name == 'name') { + return t('Create only field name %name is a disallowed field name. Changing the Tracker Name is currently not supported.', array('%name' => $name)); + } + if ($name == 'allowLinker') { + return t('Create only field name %name is a disallowed field name. Please select Multiple top-level domains under What are you tracking to enable cross domain tracking.', array('%name' => $name)); + } + if (!in_array($name, $create_only_fields)) { + return t('Create only field name %name is an unknown field name. Field names are case sensitive. Please see create only fields documentation for supported field names.', array('%name' => $name, '@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create')); + } +} + +/** + * Checks whether a field value is valid. + * + * @param string $value + * The option value entered by the user. + * + * @return string + * The error message if the specified value is invalid, NULL otherwise. + */ +function _googleanalytics_validate_create_field_value($value) { + if (!is_bool($value) && !drupal_strlen($value)) { + return t('A create only field requires a value.'); + } + if (drupal_strlen($value) > 255) { + return t('The value of a create only field must be a string at most 255 characters long.'); + } +} + +/** + * Generates a string representation of an array. + * + * This string format is suitable for edition in a textarea. + * + * @param array $values + * An array of values, where array keys are values and array values are + * labels. + * + * @return string + * The string representation of the $values array: + * - Values are separated by a carriage return. + * - Each value is in the format "name|value" or "value". + */ +function _googleanalytics_get_name_value_string($values) { + $lines = array(); + foreach ($values as $name => $value) { + // Convert data types. + // @todo: #2251377: Json utility class serializes boolean values to incorrect data type + if (is_bool($value)) { + $value = ($value) ? 'true' : 'false'; + } + + $lines[] = "$name|$value"; + } + return implode("\n", $lines); +} + +/** + * Prepare form data types for Json conversion. + * + * @param array $values + * Array of name/value pairs. + * + * @return array + * Array of name/value pairs with casted data types. + */ +function _googleanalytics_convert_form_value_data_types($values) { + + foreach ($values as $name => $value) { + // Convert data types. + // @todo: #2251377: Json utility class serializes boolean values to incorrect data type + $match = drupal_strtolower($value); + if ($match == 'true') { + $value = TRUE; + } + elseif ($match == 'false') { + $value = FALSE; + } + + // Convert other known fields. + // @todo: #2251343: Json utility class serializes numeric values to incorrect data type + switch ($name) { + case 'sampleRate': + // Float + settype($value, 'float'); + break; + + case 'siteSpeedSampleRate': + case 'cookieExpires': + // Integer + settype($value, 'integer'); + break; + } + + $values[$name] = $value; + } + + return $values; +} diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js index 35da84e2..083eeaa3 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.admin.js +++ b/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'); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.debug.js new file mode 100644 index 00000000..861f7fdb --- /dev/null +++ b/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); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info index fea44ca3..adbf134a 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install index b3841cb8..d7b2edfb 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.install +++ b/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 Google Analytics settings page.', 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 Google Analytics settings page.', 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!'); + } +} diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js index 58be03d2..1eed3c32 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.js +++ b/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); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module index 9efae875..4051b1b9 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module +++ b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.module @@ -1,19 +1,35 @@ */ -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; + 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)); - $types = array(); - $node = menu_get_object(); - if (is_object($node)) { - $types += array('node' => $node); + // Suppress empty values. + if (!drupal_strlen(trim($googleanalytics_custom_var['value']))) { + continue; + } + + // 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); + } + + // 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']) . ');'; } - $custom_var_name = token_replace($custom_var_name, $types, array('clear' => TRUE)); - $custom_var_value = token_replace($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; - } - - // 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); - } - - $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]);"; } } // 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; + 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)); } - // 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 . '";'; - } - } - $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); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test index f8a68cd4..0b64bb82 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test +++ b/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: - + */ // 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 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 enabled. + variable_set('googleanalytics_debug', 1); $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_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 Google Analytics settings page.', array('@url' => url('admin/config/system/googleanalytics'))), '[testGoogleAnalyticsConfiguration]: Debugging enabled is shown on Status Reports page.'); + + // Test whether debugging script has been disabled. + variable_set('googleanalytics_debug', 0); + $this->drupalGet(''); + $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_var', $custom_vars); - $this->verbose('
' . print_r($custom_vars, TRUE) . '
'); + variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension); + $this->verbose('
' . print_r($googleanalytics_custom_dimension, TRUE) . '
'); $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('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_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('
' . print_r($googleanalytics_custom_metric, TRUE) . '
'); + + $this->drupalGet(''); + $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 message with html tags and link.', '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'] = ''; + $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('', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.'); + + // Check tracking code visibility. + variable_set('googleanalytics_pages', ''); + $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', ''); + $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', ''); + $this->drupalGet('admin/config/system/googleanalytics'); + $this->assertRaw(t('Pages on which this PHP code returns TRUE (experts only)'), '[testGoogleAnalyticsPhpFilter]: Permission to administer PHP for tracking visibility.'); + $this->assertRaw(check_plain(''), '[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 TRUE (experts only)'), '[testGoogleAnalyticsPhpFilter]: No permission to administer PHP for tracking visibility.'); + $this->assertNoRaw(check_plain(''), '[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', ''); + + $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('', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.'); + } } diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.test.js new file mode 100644 index 00000000..05c720fb --- /dev/null +++ b/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); diff --git a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc b/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc index 36885de4..6a657350 100644 --- a/sites/all/modules/contrib/admin/google_analytics/googleanalytics.variable.inc +++ b/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.'); } } diff --git a/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt b/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt index 218bc30a..c5c66703 100644 --- a/sites/all/modules/contrib/admin/module_filter/CHANGELOG.txt +++ b/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 --------------------------------- +Simplifying the table rows by hiding version and requirements until a + particular description is clicked. + + +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. -#1933384 by kaidjohnson: Fixed jQuery UI button breaks functionality. -#1166414 by greenSkin: Fixed module filter + tabs disabled = broken modules - page. - - -Module Filter 7.x-1.7, 2012-07-05 ---------------------------------- +#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. diff --git a/sites/all/modules/contrib/admin/module_filter/README.txt b/sites/all/modules/contrib/admin/module_filter/README.txt new file mode 100644 index 00000000..0bc967ab --- /dev/null +++ b/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"'). diff --git a/sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css b/sites/all/modules/contrib/admin/module_filter/css/dynamic_position.css new file mode 100644 index 00000000..bc3b3e76 --- /dev/null +++ b/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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/module_filter.css b/sites/all/modules/contrib/admin/module_filter/css/module_filter.css index d2353ba0..19d113fb 100644 --- a/sites/all/modules/contrib/admin/module_filter/css/module_filter.css +++ b/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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css b/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab-rtl.css new file mode 100644 index 00000000..b70f6ef0 --- /dev/null +++ b/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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css b/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css index 47e73370..2607a617 100644 --- a/sites/all/modules/contrib/admin/module_filter/css/module_filter_tab.css +++ b/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: 0; + line-height: 1; +} +#module-filter-tabs li { + background: #eee; + border-top: 1px solid #ccc; + padding: 0; + margin: 0; + min-width: 0; +} +#module-filter-tabs li#new-tab { + margin-bottom: 10px; + border-bottom: 1px solid #ccc; +} +#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-tabs li.disabled a, +#module-filter-tabs li.disabled span { + color: #999; +} +#module-filter-tabs li.suggest { + background: #F9F9F9; +} +#module-filter-tabs li a { + display: block; + text-decoration: none; + padding: 10px; +} +#module-filter-tabs li a span.result-info { + float: right; + font-size: 10px; + color: #999; + margin-top: 3px; +} +#module-filter-tabs li a span.visual-aid { + font-size: 8px; +/* float: right;*/ +} +#module-filter-tabs li span.visual-aid { + font-weight: bold; +} +#module-filter-tabs li a span.enabling { + color: green; +} +#module-filter-tabs li a span.disabling { + color: red; +} +#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; +} +#module-filter-tabs li.selected .summary .count { + display: block; +} +html.js #module-filter-submit input { + margin: 2em 2em 1em; +} +html.js .module-filter-inputs-wrapper { + text-align: center; +} +html.js .module-filter-inputs-wrapper .form-item { margin: 0; padding: 9px; } -#module-filter-wrapper .form-item:after { -/* display: block;*/ - clear: none; +html.js .module-filter-inputs-wrapper label { + display: inline; } -#module-filter-left { - float: left; - background-color: #F6F6F6; - border: 1px solid #D6DBDE; - margin-right: -1px; - width: 185px; +html.js .module-filter-inputs-wrapper input[name="module_filter[name]"] { + width: 80%; } -#module-filter-left ul { - margin: 0px; - padding: 0px; - list-style: none; +html.js #module-filter-show-wrapper { + margin-bottom: 1em; } -#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; +html.js #module-filter-modules { + margin-left: 240px; + border: 1px solid #ccc; } -#module-filter-left ul li.active { - margin-right: -1px; - width: 186px; - background-color: #FFFFFF; - position: relative; +#module-filter-modules table { + border-top: none; + border-right: none; + border-left: none; } -#module-filter-left ul li a { - color: #777777; - 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; -} -#module-filter-left ul li.active a:hover { - background-color: #FFFFFF; -} -#module-filter-left ul li a span.visual-aid { - font-size: 8px; - float: right; -} -#module-filter-left ul li a span.enabling { - color: green; -} -#module-filter-left ul li a span.disabling { - color: red; - margin-left: 5px; -} -#module-filter-left ul li a span.counts { - font-weight: normal; - display: none; - font-size: 0.8em; - color: #333333; - padding: 2px 0 0; -} -#module-filter-left ul li.active a span.counts { - display: block; -} -#module-filter-submit { +html.js #module-filter-modules table { margin: 0; + border-bottom: none; } -#module-filter-submit .form-actions { - text-align: center; +html.js #module-filter-modules table tr, +html.js #module-filter-modules table td { + border-left: 0; + border-right: 0; } -#module-filter-submit input.form-submit { - margin: 1em 0 0; +#module-filter-modules table thead { + display: none; } -#module-filter-submit.fixed { - position: fixed; - background-color: #F6F6F6; - border: 1px solid #D6DBDE; - margin-left: -1px; - width: 185px; +#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; } -#module-filter-submit.fixed-top { - top: 0; +#module-filter-modules table tr.admin-package-title.first, +#module-filter-modules table tr.admin-package-title.first td { + border-top: none !important; } -#module-filter-submit.fixed-bottom { - bottom: 0; +#module-filter-modules table tr.admin-package-header td { + border: 1px solid #ccc; + text-transform: uppercase; + background: #E1E2DC; + font-weight: normal; + padding: 3px 10px; } -#module-filter-right { - display: block; -} -#module-filter-squeeze { - margin-left: 186px; - background-color: #FFFFFF; - border: 1px solid #D6DBDE; - height: auto !important; -} -.form-item-module-filter-name { - text-align: center; -} -.form-item-module-filter-name label { - display: inline; -} -#module-filter-show-wrapper .form-checkboxes { - text-align: center; -} -#module-filter-show-wrapper .form-item:after { - display: inline; -} -table.package { - margin: 1em 0; -} -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; -} -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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/modules.css b/sites/all/modules/contrib/admin/module_filter/css/modules.css new file mode 100644 index 00000000..91fa7418 --- /dev/null +++ b/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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/css/update_status.css b/sites/all/modules/contrib/admin/module_filter/css/update_status.css new file mode 100644 index 00000000..c0bee443 --- /dev/null +++ b/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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js b/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js index 10c66b4f..41c57312 100644 --- a/sites/all/modules/contrib/admin/module_filter/js/dynamic_position.js +++ b/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); diff --git a/sites/all/modules/contrib/admin/module_filter/js/module_filter.js b/sites/all/modules/contrib/admin/module_filter/js/module_filter.js index 49f6b7a0..d5f53287 100644 --- a/sites/all/modules/contrib/admin/module_filter/js/module_filter.js +++ b/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); - } +Drupal.ModuleFilter = {}; - moduleFilter(moduleFilterTextFilter); - break; - default: - if (moduleFilterTextFilter != $(this).val()) { - moduleFilterTextFilter = this.value; - if (moduleFilterTimeOut) { - clearTimeout(moduleFilterTimeOut); - } +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; +}; - moduleFilterTimeOut = setTimeout('moduleFilter("' + moduleFilterTextFilter + '")', 500); - } - break; +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; +}; - moduleFilter = function(string) { - stringLowerCase = string.toLowerCase(); +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: '/' }); + } +}; - $("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'); +Drupal.ModuleFilter.Filter = function(element, selector, options) { + var self = this; - if (string != '') { - if ($fieldset.hasClass('collapsed')) { - $fieldset.removeClass('collapsed'); + this.element = element; + this.text = $(this.element).val(); + + this.settings = Drupal.settings.moduleFilter; + + this.selector = selector; + + 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(''); + 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; + }); + + 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(); + var func = self.operators[query.operator]; + if (!(func(query.string, self, item))) { + continue; } } - } - else { - if ($row.is(':visible')) { - $row.hide(); - if ($row.siblings(':visible').html() == null) { - $fieldset.hide(); - } - } - } - }); - } - 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($('

').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; + } + }); + + 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; } } - if ($('#edit-module-filter-show-unavailable').is(':checked')) { - if (checkbox.length == 0 || ($(checkbox).size() > 0 && !$(checkbox).is(':checked') && $(checkbox).is(':disabled'))) { - return true; - } - } - 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); diff --git a/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js b/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js index a6024453..aa54b983 100644 --- a/sites/all/modules/contrib/admin/module_filter/js/module_filter_tab.js +++ b/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(); +Drupal.ModuleFilter.tabs = {}; +Drupal.ModuleFilter.enabling = {}; +Drupal.ModuleFilter.disabling = {}; - $('#module-filter-squeeze').css('min-height', $('#module-filter-tabs').height()); +Drupal.ModuleFilter.jQueryIsNewer = function() { + if (Drupal.ModuleFilter.jQueryNewer == undefined) { + var v1parts = $.fn.jquery.split('.'); + var v2parts = new Array('1', '4', '4'); - $('#module-filter-left a.project-tab').each(function(i) { - Drupal.ModuleFilter.tabs[$(this).attr('id')] = new Drupal.ModuleFilter.Tab(this); - }); + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length == i) { + Drupal.ModuleFilter.jQueryNewer = true; + return Drupal.ModuleFilter.jQueryNewer; + } - // Move anchors to top of tabs. - $('a.anchor', $('#module-filter-left')).remove().prependTo('#module-filter-tabs'); + 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; + } + } - $('input[name="module_filter[name]"]').keyup(function(e) { - switch (e.which) { - case 13: - if (Drupal.ModuleFilter.timeout) { - clearTimeout(Drupal.ModuleFilter.timeout); - } + if (v1parts.length != v2parts.length) { + Drupal.ModuleFilter.jQueryNewer = false; + return Drupal.ModuleFilter.jQueryNewer; + } - 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); + 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(); + + var $tabsWrapper = $('

'); + + // Build tabs from package title rows. + var tabs = '
    '; + 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) ? '' + Drupal.ModuleFilter.countSummary(id) + '' : ''; + + 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 += '' + Drupal.t('No modules added within the last week.') + ''; } - 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 == 1) { - $('table.package tbody td.checkbox input').change(function() { - if ($(this).is(':checked')) { - Drupal.ModuleFilter.updateVisualAid('enable', $(this).parents('tr')); + 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 += '' + Drupal.t('No modules were enabled or disabled within the last week.') + ''; + } + break; + default: + var $row = $('#' + id + '-package'); + name = $.trim($row.text()); + $row.remove(); + break; } - else { - Drupal.ModuleFilter.updateVisualAid('disable', $(this).parents('tr')); + + tabs += '
  • ' + name + '' + summary + '
  • '; + } + tabs += '
'; + $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')); } }); - } - // 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']; + // 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); + + 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'); + } + } + } + }); + + 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 (tab.resultInfo == undefined) { + var resultInfo = '' + $('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(); + } + } + } + + 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(); + }); + + if (Drupal.settings.moduleFilter.useURLFragment) { + $(window).bind('hashchange.module-filter', $.proxy(Drupal.ModuleFilter, 'eventHandlerOperateByURLFragment')).triggerHandler('hashchange.module-filter'); + } + else { + Drupal.ModuleFilter.selectTab(); + } + + 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'); + } + }); + }); + } + + 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; + } + + var tabsTopOffset = null; + function getParentsTopOffset() { + if (tabsTopOffset === null) { + tabsTopOffset = getParentTopOffset($tabs.parent(), 0); + } + return tabsTopOffset; + } + + function viewportTop() { + var top = $(window).scrollTop(); + return top; + } + + function viewportBottom() { + var top = $(window).scrollTop(); + var bottom = top + $(window).height(); + + bottom -= $('#page-actions').height(); + + return bottom; + } + + 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); + } + } + + var lastTop = 0; + $(window).scroll(function() { + var top = viewportTop(); + var bottom = viewportBottom(); + + 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; + } + + if (self.element.hasClass('selected')) { + // Clear the active tab. + window.location.hash = 'all'; + return false; + } + }); + + $('tr.' + this.id, $('#system-modules')).hover( + function() { + self.element.addClass('suggest'); + }, + function() { + self.element.removeClass('suggest'); + } + ); +}; + +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; } } + + if (!hash) { + hash = 'all'; + } } - Drupal.ModuleFilter.visible = function(checkbox) { - if (checkbox.length > 0) { - if (Drupal.ModuleFilter.showEnabled) { - if ($(checkbox).is(':checked') && !$(checkbox).is(':disabled')) { - return true; - } + if (Drupal.ModuleFilter.activeTab != undefined) { + Drupal.ModuleFilter.activeTab.element.removeClass('selected'); + } + + 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.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.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 (enabling.length > 0) { + enabling.sort(); + visualAid += '+' + enabling.join(', ') + ''; + } + } + 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 += '
'; } - if (Drupal.ModuleFilter.showDisabled) { - if (!$(checkbox).is(':checked') && !$(checkbox).is(':disabled')) { - return true; - } - } - if (Drupal.ModuleFilter.showRequired) { - if ($(checkbox).is(':checked') && $(checkbox).is(':disabled')) { - return true; - } - } - } - if (Drupal.ModuleFilter.showUnavailable) { - if (checkbox.length == 0 || (!$(checkbox).is(':checked') && $(checkbox).is(':disabled'))) { - return true; + visualAid += '-' + disabling.join(', ') + ''; + } + } + + if (this.visualAid == undefined) { + $('a span.summary', this.element).append(''); + this.visualAid = $('span.visual-aid', this.element); + } + + 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) { + if (Drupal.ModuleFilter.tabs[classes[i]] != undefined) { + id = classes[i]; + break; } } + $row.data('moduleFilterTabID', id); + } + return id; +}; + +Drupal.ModuleFilter.updateVisualAid = function(type, $row) { + var id = Drupal.ModuleFilter.getTabID($row); + + if (!id) { return false; } - Drupal.ModuleFilter.filter = function(string) { - var stringLowerCase = string.toLowerCase(); - var flip = 'odd'; - - 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'; - } - - $(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'; - } - else { - $row.hide(); - } + 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 { - $row.hide(); + Drupal.ModuleFilter.enabling[id + name] = name; + tab.updateEnabling(name); + $row.addClass('enabling'); } - }); - } - - Drupal.ModuleFilter.Tab = function(element) { - this.id = $(element).attr('id'); - this.element = element; - - $(this.element).click(function() { - Drupal.ModuleFilter.tabs[$(this).attr('id')].setActive(); - }); - } - - Drupal.ModuleFilter.Tab.prototype.setActive = function() { - if (Drupal.ModuleFilter.activeTab) { - $(Drupal.ModuleFilter.activeTab.element).parent().removeClass('active'); - } - // Assume the default active tab is #all-tab. Remove its active class. - else { - $('#all-tab').parent().removeClass('active'); - } - - Drupal.ModuleFilter.activeTab = this; - $(Drupal.ModuleFilter.activeTab.element).parent().addClass('active'); - Drupal.ModuleFilter.activeTab.displayRows(); - - // Clear filter textfield and refocus on it. - $('input[name="module_filter[name]"]').val(''); - $('input[name="module_filter[name]"]').focus(); - } - - 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(); + break; + case 'disable': + if (Drupal.ModuleFilter.enabling[id + name] != undefined) { + delete Drupal.ModuleFilter.enabling[id + name]; + tab.updateEnabling(name, true); + $row.removeClass('enabling'); } - }); - } - - Drupal.ModuleFilter.Tab.prototype.updateEnabling = function(amount) { - this.enabling = this.enabling || 0; - this.enabling += amount; - if (this.enabling == 0) { - delete(this.enabling); - } - } - - Drupal.ModuleFilter.Tab.prototype.updateDisabling = function(amount) { - this.disabling = this.disabling || 0; - this.disabling += amount; - if (this.disabling == 0) { - delete(this.disabling); - } - } - - Drupal.ModuleFilter.Tab.prototype.updateVisualAid = function() { - var visualAid = ''; - if (this.enabling != undefined) { - visualAid += '' + Drupal.t('+@count', { '@count': this.enabling }) + ''; - } - if (this.disabling != undefined) { - visualAid += '' + Drupal.t('-@count', { '@count': this.disabling }) + ''; - } - - if (!$('span.visual-aid', $(this.element)).size() && visualAid != '') { - $(this.element).prepend(''); - } - - $('span.visual-aid', $(this.element)).empty().append(visualAid); - } - - Drupal.ModuleFilter.updateVisualAid = function(type, row) { - // Find row class. - 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) { - break; + else { + Drupal.ModuleFilter.disabling[id + name] = name; + tab.updateDisabling(name); + $row.addClass('disabling'); } - } - - 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; - } - - 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; - } - - allTab.updateVisualAid(); - projectTab.updateVisualAid(); + 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); diff --git a/sites/all/modules/contrib/admin/module_filter/js/modules.js b/sites/all/modules/contrib/admin/module_filter/js/modules.js new file mode 100644 index 00000000..7df5f87f --- /dev/null +++ b/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); diff --git a/sites/all/modules/contrib/admin/module_filter/js/permissions.js b/sites/all/modules/contrib/admin/module_filter/js/permissions.js new file mode 100644 index 00000000..83403897 --- /dev/null +++ b/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); \ No newline at end of file diff --git a/sites/all/modules/contrib/admin/module_filter/js/update_status.js b/sites/all/modules/contrib/admin/module_filter/js/update_status.js new file mode 100644 index 00000000..fcd36107 --- /dev/null +++ b/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); diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc b/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc index 0763c9c1..2345d8de 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.admin.inc +++ b/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.
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.') . '
' . 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.
Modules will not actually be enabled/disabled until the form is saved.'), + '#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'] .= '
' . t('The module %name is enabled and thus this setting will have no affect.', array('%name' => t('Page actions'))); + } + return system_settings_form($form); } diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.info b/sites/all/modules/contrib/admin/module_filter/module_filter.info index 89fe45d5..a48b58f3 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.install b/sites/all/modules/contrib/admin/module_filter/module_filter.install index c06cfd83..c39b3d12 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.install +++ b/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'); +} diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.module b/sites/all/modules/contrib/admin/module_filter/module_filter.module index 09b7542c..42b39135 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.module +++ b/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' => '
', '#suffix' => '
' @@ -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) - ) - ); - - $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); - } - } - - // Sort the array of modules alphabetically. - uasort($modules, 'module_filter_sort_modules_by_display_name'); - - // Replace the $form['modules'] with our $modules array. - $form['modules'] = $modules; - - // Add our $tab_counts array to the form. - $form['#tab_counts'] = $tab_counts; + $form['#attached']['css'][] = drupal_get_path('module', 'module_filter') . '/css/modules.css'; $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'] = ''; + + $form['#submit'][] = 'module_filter_system_modules_submit_redirect'; + + if (variable_get('module_filter_track_recent_modules', 1)) { + $form['#submit'][] = 'module_filter_system_modules_submit_recent'; } } /** - * Implementation of hook_theme(). + * 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, + ); +} + +/** + * 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; +} + +/** + * 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 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 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 0; + return $element; +} + +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']; + } + $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)); } diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc b/sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc new file mode 100644 index 00000000..a4c23495 --- /dev/null +++ b/sites/all/modules/contrib/admin/module_filter/module_filter.pages.inc @@ -0,0 +1,46 @@ + 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' => '
', + '#suffix' => '
' + ); + 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; +} diff --git a/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc b/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc index 59cce42d..b199bedf 100644 --- a/sites/all/modules/contrib/admin/module_filter/module_filter.theme.inc +++ b/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 = '' . drupal_render($module['name']) . ''; - $row[] = drupal_render($module['version']); - // Add the description, along with any modules it requires. - $description = drupal_render($module['description']); - if ($module['#requires']) { - $description .= '
' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '
'; - } - if ($module['#required_by']) { - $description .= '
' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '
'; - } - $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 '
' . drupal_render_children($element) . '
'; } /** * Theme callback for the modules tabbed form. */ function theme_module_filter_system_modules_tabs($variables) { - $form = $variables['form']; - - $count_enabled = variable_get('module_filter_count_enabled', 1); - - // Display packages. - $all = t('All'); - $all_count = ($count_enabled) ? '' . t('!enabled of !total', array('!enabled' => $form['#tab_counts'][$all]['enabled'], '!total' => $form['#tab_counts'][$all]['total'])) . '' : ''; - $tabs = array('all' => '
  • ' . $all . $all_count . '
  • '); - foreach ($form['#packages'] as $package) { - $id = module_filter_get_id($package); - - $count = ($count_enabled) ? '' . t('!enabled of !total', array('!enabled' => $form['#tab_counts'][$package]['enabled'], '!total' => $form['#tab_counts'][$package]['total'])) . '' : ''; - $tabs[$id] = '
  • ' . $package . $count . '
  • '; + 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']; + + if (!module_exists('page_actions')) { + $form['actions']['#prefix'] = '
    '; + $form['actions']['#suffix'] = '
    '; + } + + $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' => '

    ' . $form['modules'][$package]['#title'] . '

    ', '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]; + + $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; + } + + $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 = '
     
    '; + } + $row[] = array('class' => array('checkbox'), 'data' => $toggle_enable . drupal_render($module['enable'])); + + $label = ' array('name'), 'data' => $label . '>' . drupal_render($module['name']) . ' (' . $key . ')'); + + // Add the description, along with any modules it requires. + $description = '' . drupal_render($module['description']) . ''; + if ($version || $requires || $required_by) { + $description .= '
    '; + if ($version) { + $description .= '
    ' . t('Version: !module-version', array('!module-version' => drupal_render($module['version']))) . '
    '; + } + if ($requires) { + $description .= '
    ' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '
    '; + } + if ($required_by) { + $description .= '
    ' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '
    '; + } + $description .= '
    '; + } + $row[] = array('data' => '
    ' . $description . '
    ', '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' => '', '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 = '
    '; - $output .= '
    '; - $output .= '
      '. implode($tabs) . '
    '; - $output .= '
    ' . drupal_render($form['actions']) . '
    '; - $output .= '
    ' . drupal_render($form['module_filter']); - $output .= drupal_render($form['modules']) . '
    '; - $output .= '
    '; - $output .= '
    '; + $output .= '
    ' . drupal_render($form['module_filter']); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); $output .= drupal_render_children($form); + $output .= '
    '; + $output .= '
    '; 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 '
    ' . theme('links__ctools_dropbutton', array('title' => t('Operations'), 'links' => $operations, 'attributes' => array('class' => array('links')))) . '
    '; + } + return '
    ' . theme('item_list', array('items' => $operations, 'attributes' => array('class' => array('links', 'inline')))) . '
    '; + } +} diff --git a/sites/all/modules/contrib/admin/override_node_options/LICENSE.txt b/sites/all/modules/contrib/admin/override_node_options/LICENSE.txt index 2c095c8d..d159169d 100644 --- a/sites/all/modules/contrib/admin/override_node_options/LICENSE.txt +++ b/sites/all/modules/contrib/admin/override_node_options/LICENSE.txt @@ -1,274 +1,339 @@ -GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - 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. -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 - 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. -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. -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. -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. -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. -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. -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. -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. -The precise terms and conditions for copying, distribution and modification -follow. + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 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". -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. -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. -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. -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: -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. -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. -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.) -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. -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. +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. +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: + 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, + 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, + 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.) + 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. +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. +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. + 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. + 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. + 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 + 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. +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. +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. +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. + 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. + 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. +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. + 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 + 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. + 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. + 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 + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/override_node_options/override_node_options.info b/sites/all/modules/contrib/admin/override_node_options/override_node_options.info index a948730a..b2f8a14d 100644 --- a/sites/all/modules/contrib/admin/override_node_options/override_node_options.info +++ b/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" diff --git a/sites/all/modules/contrib/admin/override_node_options/override_node_options.install b/sites/all/modules/contrib/admin/override_node_options/override_node_options.install index ac3fd65b..9138b48c 100644 --- a/sites/all/modules/contrib/admin/override_node_options/override_node_options.install +++ b/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(); +} diff --git a/sites/all/modules/contrib/admin/override_node_options/override_node_options.module b/sites/all/modules/contrib/admin/override_node_options/override_node_options.module index 18d780f8..5bf6252b 100644 --- a/sites/all/modules/contrib/admin/override_node_options/override_node_options.module +++ b/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']) { diff --git a/sites/all/modules/contrib/dev/devel/README.txt b/sites/all/modules/contrib/dev/devel/README.txt index f93f815b..194fcbf5 100644 --- a/sites/all/modules/contrib/dev/devel/README.txt +++ b/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. diff --git a/sites/all/modules/contrib/dev/devel/devel.admin.inc b/sites/all/modules/contrib/dev/devel/devel.admin.inc index a8a8b2c6..6fec2ca5 100644 --- a/sites/all/modules/contrib/dev/devel/devel.admin.inc +++ b/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', diff --git a/sites/all/modules/contrib/dev/devel/devel.drush.inc b/sites/all/modules/contrib/dev/devel/devel.drush.inc index 148e85c2..b4d85054 100644 --- a/sites/all/modules/contrib/dev/devel/devel.drush.inc +++ b/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. */ diff --git a/sites/all/modules/contrib/dev/devel/devel.info b/sites/all/modules/contrib/dev/devel/devel.info index 2ace43f4..f53d43d6 100644 --- a/sites/all/modules/contrib/dev/devel/devel.info +++ b/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" diff --git a/sites/all/modules/contrib/dev/devel/devel.install b/sites/all/modules/contrib/dev/devel/devel.install index 03fc0ae3..f0828574 100644 --- a/sites/all/modules/contrib/dev/devel/devel.install +++ b/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'); +} diff --git a/sites/all/modules/contrib/dev/devel/devel.js b/sites/all/modules/contrib/dev/devel/devel.js index bbf4942b..c23cc943 100644 --- a/sites/all/modules/contrib/dev/devel/devel.js +++ b/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); diff --git a/sites/all/modules/contrib/dev/devel/devel.mail.inc b/sites/all/modules/contrib/dev/devel/devel.mail.inc index 4d3a7213..d6f9ad53 100644 --- a/sites/all/modules/contrib/dev/devel/devel.mail.inc +++ b/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; } diff --git a/sites/all/modules/contrib/dev/devel/devel.module b/sites/all/modules/contrib/dev/devel/devel.module index 0ebdef4d..624ac7d3 100644 --- a/sites/all/modules/contrib/dev/devel/devel.module +++ b/sites/all/modules/contrib/dev/devel/devel.module @@ -1,10 +1,12 @@ '. t('This is a list of defined user functions that generated this current request lifecycle. Click on a function name to view its documention.') .'

    '; + return '

    ' . t('This is a list of defined user functions that generated this current request lifecycle. Click on a function name to view its documentation.') . '

    '; case 'devel/session': - return '

    '. t('Here are the contents of your $_SESSION variable.') .'

    '; + return '

    ' . t('Here are the contents of your $_SESSION variable.') . '

    '; case 'devel/variable': $api = variable_get('devel_api_url', 'api.drupal.org'); - return '

    '. t('This is a list of the variables and their values currently stored in variables table and the $conf array of your settings.php file. These variables are usually accessed with variable_get() and variable_set(). Variables that are too long can slow down your pages.', array('@variable-get-doc' => "http://$api/api/HEAD/function/variable_get", '@variable-set-doc' => "http://$api/api/HEAD/function/variable_set")) .'

    '; + return '

    ' . t('This is a list of the variables and their values currently stored in variables table and the $conf array of your settings.php file. These variables are usually accessed with variable_get() and variable_set(). Variables that are too long can slow down your pages.', array('@variable-get-doc' => "http://$api/api/HEAD/function/variable_get", '@variable-set-doc' => "http://$api/api/HEAD/function/variable_set")) . '

    '; case 'devel/reinstall': return t('Warning - will delete your module tables and variables.'); } @@ -52,9 +54,10 @@ function devel_modules_installed($modules) { * Implements hook_menu(). */ function devel_menu() { - // Note: we can't dynamically append destination to querystring. Do so at theme layer. Fix in D7? + // Note: we can't dynamically append destination to querystring. + // Do so at theme layer. Fix in D7? $items['devel/cache/clear'] = array( - 'title' => 'Empty cache', + 'title' => 'Clear cache', 'page callback' => 'devel_cache_clear', 'description' => 'Clear the CSS cache and all database cache tables which store page, node, theme and variable caches.', 'access arguments' => array('access devel information'), @@ -99,12 +102,13 @@ function devel_menu() { $items['devel/variable'] = array( 'title' => 'Variable editor', 'description' => 'Edit and delete site variables.', - 'page callback' => 'devel_variable_page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('devel_variable_form'), 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', 'menu_name' => 'devel', ); - // we don't want the abbreviated version provided by status report + // We don't want the abbreviated version provided by status report. $items['devel/phpinfo'] = array( 'title' => 'PHPinfo()', 'description' => 'View your server\'s PHP configuration', @@ -186,7 +190,7 @@ function devel_menu() { 'description' => 'Run an EXPLAIN on a given query. Used by query log', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', - 'type' => MENU_CALLBACK + 'type' => MENU_CALLBACK, ); $items['devel/arguments'] = array( 'title' => 'Arguments query', @@ -194,7 +198,7 @@ function devel_menu() { 'description' => 'Return a given query, with arguments instead of placeholders. Used by query log', 'access arguments' => array('access devel information'), 'file' => 'devel.pages.inc', - 'type' => MENU_CALLBACK + 'type' => MENU_CALLBACK, ); $items['devel/run-cron'] = array( 'title' => 'Run cron', @@ -208,7 +212,7 @@ function devel_menu() { // Duplicate path in 2 different menus. See http://drupal.org/node/601788. $items['devel/settings'] = array( 'title' => 'Devel settings', - 'description' => 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.', + 'description' => 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_admin_settings'), 'access arguments' => array('administer site configuration'), @@ -217,7 +221,7 @@ function devel_menu() { ); $items['admin/config/development/devel'] = array( 'title' => 'Devel settings', - 'description' => 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.', + 'description' => 'Helper functions, pages, and blocks to assist Drupal developers. The devel blocks can be managed via the block administration page.', 'page callback' => 'drupal_get_form', 'page arguments' => array('devel_admin_settings'), 'file' => 'devel.admin.inc', @@ -342,14 +346,20 @@ function devel_admin_paths() { return $paths; } +/** + * Returns destinations. + */ function devel_menu_need_destination() { - return array('devel/cache/clear', 'devel/reinstall', 'devel/menu/reset', 'devel/variable', 'admin/reports/status/run-cron'); + return array('devel/cache/clear', 'devel/reinstall', 'devel/menu/reset', + 'devel/variable', 'admin/reports/status/run-cron'); } /** - * An implementation of hook_menu_link_alter(). Flag this link as needing alter at display time. + * Implements hook_menu_link_alter(). + * + * Flag this link as needing alter at display time. * This is more robust than setting alter in hook_menu(). - * @see devel_translated_menu_link_alter(). + * @see devel_translated_menu_link_alter() * **/ function devel_menu_link_alter(&$item) { @@ -359,9 +369,9 @@ function devel_menu_link_alter(&$item) { } /** - * An implementation of hook_translated_menu_item_alter(). Append dynamic - * querystring 'destination' to several of our own menu items. + * Implements hook_translated_menu_item_alter(). * + * Append dynamic querystring 'destination' to several of our own menu items. **/ function devel_translated_menu_link_alter(&$item) { if (in_array($item['href'], devel_menu_need_destination())) { @@ -373,7 +383,7 @@ function devel_translated_menu_link_alter(&$item) { } /** - * Implementation of hook_theme() + * Implements hook_theme(). */ function devel_theme() { return array( @@ -387,18 +397,19 @@ function devel_theme() { } /** - * Implementation of hook_init(). + * Implements hook_init(). */ function devel_init() { if (!devel_silent()) { if (user_access('access devel information')) { devel_set_handler(devel_get_handlers()); - // We want to include the class early so that anyone may call krumo() as needed. See http://krumo.sourceforge.net/ + // We want to include the class early so that anyone may call krumo() + // as needed. See http://krumo.sourceforge.net/ has_krumo(); // See http://www.firephp.org/HQ/Install.htm $path = NULL; - if (@include_once('fb.php')) { + if ((@include_once 'fb.php') || (@include_once 'FirePHPCore/fb.php')) { // FirePHPCore is in include_path. Probably a PEAR installation. $path = ''; } @@ -424,7 +435,6 @@ function devel_init() { include_once $chromephp_path; } - // Add CSS for query log if should be displayed. if (variable_get('devel_query_display', 0)) { drupal_add_css(drupal_get_path('module', 'devel') . '/devel.css'); @@ -443,14 +453,18 @@ function devel_init() { } } +/** + * Sets message. + */ function devel_set_message($msg, $type = NULL) { $function = function_exists('drush_log') ? 'drush_log' : 'drupal_set_message'; $function($msg, $type); -} + } -// Return boolean. No need for cache here. +/** + * Returns boolean. No need for cache here. + */ function has_krumo() { - // see README.txt or just download from http://krumo.sourceforge.net/ @include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'devel') . '/krumo/class.krumo.php'; if (function_exists('krumo') && !drupal_is_cli()) { drupal_add_js(drupal_get_path('module', 'devel') . '/devel_krumo_path.js'); @@ -460,9 +474,11 @@ function has_krumo() { } /** - * Decide whether or not to print a debug variable using krumo(). + * Decides whether or not to print a debug variable using krumo(). + * + * @param array|object $input + * The value to check. * - * @param $input * @return boolean */ function merits_krumo($input) { @@ -471,8 +487,6 @@ function merits_krumo($input) { /** * Calls the http://www.firephp.org/ fb() function if it is found. - * - * @return void */ function dfb() { if (function_exists('fb') && user_access('access devel information') && !headers_sent()) { @@ -489,7 +503,7 @@ function dfbt($label) { } /** - * Wrapper for ChromePHP Class log method + * Wrapper for ChromePHP Class log method. */ function dcp() { if (class_exists('ChromePhp', FALSE) && user_access('access devel information')) { @@ -510,15 +524,18 @@ function devel_watchdog(array $log_entry) { case WATCHDOG_ERROR: $type = FirePHP::ERROR; break; + case WATCHDOG_WARNING: $type = FirePHP::WARN; break; + case WATCHDOG_NOTICE: case WATCHDOG_INFO: $type = FirePHP::INFO; break; + case WATCHDOG_DEBUG: - DEFAULT: + default: $type = FirePHP::LOG; } } @@ -528,7 +545,7 @@ function devel_watchdog(array $log_entry) { $function = function_exists('decode_entities') ? 'decode_entities' : 'html_entity_decode'; $watchdog = array( 'type' => $log_entry['type'], - 'message' => $function(strtr($log_entry['message'], (array)$log_entry['variables'])), + 'message' => $function(strtr($log_entry['message'], (array) $log_entry['variables'])), ); if (isset($log_entry['link'])) { $watchdog['link'] = $log_entry['link']; @@ -536,6 +553,9 @@ function devel_watchdog(array $log_entry) { dfb($watchdog, $type); } +/** + * Gets error handlers. + */ function devel_get_handlers() { $error_handlers = variable_get('devel_error_handlers', array(DEVEL_ERROR_HANDLER_STANDARD => DEVEL_ERROR_HANDLER_STANDARD)); if (!empty($error_handlers)) { @@ -544,12 +564,15 @@ function devel_get_handlers() { return $error_handlers; } +/** + * Sets a new error handler or restores the prior one. + */ function devel_set_handler($handlers) { if (empty($handlers)) { restore_error_handler(); } elseif (count($handlers) == 1 && isset($handlers[DEVEL_ERROR_HANDLER_STANDARD])) { - // do nothing + // Do nothing. } else { if (has_krumo()) { @@ -558,6 +581,9 @@ function devel_set_handler($handlers) { } } +/** + * Checks whether Devel may be active. + */ function devel_silent() { // isset($_GET['q']) is needed when calling the front page. q is not set. // Don't interfere with private files/images. @@ -568,13 +594,16 @@ function devel_silent() { isset($GLOBALS['devel_shutdown']) || strstr($_SERVER['PHP_SELF'], 'update.php') || (isset($_GET['q']) && ( - in_array($_GET['q'], array( 'admin/content/node-settings/rebuild')) || + in_array($_GET['q'], array('admin/content/node-settings/rebuild')) || substr($_GET['q'], 0, strlen('system/files')) == 'system/files' || substr($_GET['q'], 0, strlen('batch')) == 'batch' || substr($_GET['q'], 0, strlen('file/ajax')) == 'file/ajax') ); } +/** + * Enables xhprof. + */ function devel_xhprof_enable() { if (devel_xhprof_is_enabled()) { if ($path = variable_get('devel_xhprof_directory', '')) { @@ -586,19 +615,24 @@ function devel_xhprof_enable() { } } +/** + * Checks if xhprof is enabled. + */ function devel_xhprof_is_enabled() { return extension_loaded('xhprof') && variable_get('devel_xhprof_enabled', FALSE); } /** - * Implementation of hook_boot(). Runs even for cached pages. + * Implements hook_boot(). + * + * Runs even for cached pages. */ function devel_boot() { // Initialize XHProf. devel_xhprof_enable(); if (!devel_silent()) { - if (variable_get('dev_mem', 0)) { + if (variable_get('devel_memory', 0)) { global $memory_init; $memory_init = memory_get_usage(); } @@ -620,15 +654,15 @@ function devel_boot() { /** * Displays backtrace showing the route of calls to the current error. * - * @param $error_level + * @param int $error_level * The level of the error raised. - * @param $message + * @param string $message * The error message. - * @param $filename + * @param string $filename * The filename that the error was raised in. - * @param $line + * @param int $line * The line number the error was raised at. - * @param $context + * @param array $context * An array that points to the active symbol table at the point the error * occurred. */ @@ -642,7 +676,8 @@ function backtrace_error_handler($error_level, $message, $filename, $line, $cont if ($error_level & error_reporting()) { $types = drupal_error_levels(); list($severity_msg, $severity_level) = $types[$error_level]; - $caller = _drupal_get_last_caller(debug_backtrace()); + $backtrace = debug_backtrace(); + $caller = _drupal_get_last_caller($backtrace); if (!function_exists('filter_xss_admin')) { require_once DRUPAL_ROOT . '/includes/common.inc'; } @@ -683,7 +718,14 @@ function backtrace_error_handler($error_level, $message, $filename, $line, $cont $types = drupal_error_levels(); $type = $types[$error_level]; $backtrace = debug_backtrace(); - $variables = array('%error' => $type[0], '%message' => $message, '%function' => $backtrace[1]['function'] .'()', '%file' => $filename, '%line' => $line); + $variables = array( + '%error' => $type[0], + '%message' => $message, + '%function' => $backtrace[1]['function'] . '()', + '%file' => $filename, + '%line' => $line, + ); + $msg = t('%error: %message in %function (line %line of %file).', $variables); // Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher. @@ -696,7 +738,7 @@ function backtrace_error_handler($error_level, $message, $filename, $line, $cont drupal_set_message($msg, ($type[1] <= WATCHDOG_ERROR ? 'error' : 'warning')); } if (!empty($error_handlers[DEVEL_ERROR_HANDLER_BACKTRACE_KRUMO])) { - print $msg ." =>\n"; + print $msg . " =>\n"; ddebug_backtrace(FALSE, 1); } if (!empty($error_handlers[DEVEL_ERROR_HANDLER_BACKTRACE_DPM])) { @@ -710,7 +752,7 @@ function backtrace_error_handler($error_level, $message, $filename, $line, $cont } /** - * Implement hook_permission(). + * Implements hook_permission(). */ function devel_permission() { return array( @@ -732,6 +774,9 @@ function devel_permission() { ); } +/** + * Implements hook_block_info(). + */ function devel_block_info() { $blocks['execute_php'] = array( 'info' => t('Execute PHP'), @@ -745,7 +790,7 @@ function devel_block_info() { } /** - * Implementation of hook_block_configure(). + * Implements hook_block_configure(). */ function devel_block_configure($delta) { if ($delta == 'switch_user') { @@ -770,6 +815,9 @@ function devel_block_configure($delta) { } } +/** + * Implements hook_block_save(). + */ function devel_block_save($delta, $edit = array()) { if ($delta == 'switch_user') { variable_set('devel_switch_user_list_size', $edit['list_size']); @@ -778,6 +826,9 @@ function devel_block_save($delta, $edit = array()) { } } +/** + * Implements hook_block_view(). + */ function devel_block_view($delta) { $block = array(); switch ($delta) { @@ -794,6 +845,9 @@ function devel_block_view($delta) { return $block; } +/** + * Provides the Switch user block. + */ function devel_block_switch_user() { $links = devel_switch_user_list(); if (!empty($links) || user_access('switch users')) { @@ -808,13 +862,16 @@ function devel_block_switch_user() { } } +/** + * Provides the Switch user list. + */ function devel_switch_user_list() { global $user; $links = array(); if (user_access('switch users')) { $list_size = variable_get('devel_switch_user_list_size', 10); - if ($include_anon = ($user->uid && variable_get('devel_switch_user_include_anon', FALSE))) { + if ($include_anon = variable_get('devel_switch_user_include_anon', FALSE)) { --$list_size; } $dest = drupal_get_destination(); @@ -844,11 +901,11 @@ function devel_switch_user_list() { $accounts = user_load_multiple($uids); foreach ($accounts as $account) { - $path = 'devel/switch/'. $account->name; + $path = 'devel/switch/' . $account->name; $links[$account->uid] = array( 'title' => drupal_placeholder(format_username($account)), 'href' => $path, - 'query' => array($dest, 'token' => drupal_get_token($path . '|' . $dest['destination'])), + 'query' => $dest + array('token' => drupal_get_token($path . '|' . $dest['destination'])), 'attributes' => array('title' => t('This user can switch back.')), 'html' => TRUE, 'last_access' => $account->access, @@ -860,11 +917,11 @@ function devel_switch_user_list() { $uids = db_query_range('SELECT uid FROM {users} WHERE uid > 0 AND uid NOT IN (:uids) AND status > 0 ORDER BY access DESC', 0, $list_size - $num_links, array(':uids' => array_keys($links)))->fetchCol(); $accounts = user_load_multiple($uids); foreach ($accounts as $account) { - $path = 'devel/switch/'. $account->name; + $path = 'devel/switch/' . $account->name; $links[$account->uid] = array( 'title' => format_username($account), 'href' => $path, - 'query' => array($dest, 'token' => drupal_get_token($path . '|' . $dest['destination'])), + 'query' => $dest + array('token' => drupal_get_token($path . '|' . $dest['destination'])), 'attributes' => array('title' => t('Caution: this user will be unable to switch back.')), 'last_access' => $account->access, ); @@ -876,7 +933,7 @@ function devel_switch_user_list() { $link = array( 'title' => format_username(drupal_anonymous_user()), 'href' => $path, - 'query' => array($dest, 'token' => drupal_get_token($path . '/|' . $dest['destination'])), + 'query' => $dest + array('token' => drupal_get_token($path . '/|' . $dest['destination'])), 'attributes' => array('title' => t('Caution: the anonymous user will be unable to switch back.')), ); if (user_access('switch users', drupal_anonymous_user())) { @@ -902,6 +959,9 @@ function _devel_switch_user_list_cmp($a, $b) { return $b['last_access'] - $a['last_access']; } +/** + * Provides the Switch user form. + */ function devel_switch_user_form() { $form['username'] = array( '#type' => 'textfield', @@ -918,6 +978,9 @@ function devel_switch_user_form() { return $form; } +/** + * Provides the devel docs form. + */ function devel_doc_function_form() { $version = devel_get_core_version(VERSION); $form['function'] = array( @@ -934,19 +997,28 @@ function devel_doc_function_form() { return $form; } +/** + * Submit handler for the API lookup form. + */ function devel_doc_function_form_submit($form, &$form_state) { $version = $form_state['values']['version']; $function = $form_state['values']['function']; $api = variable_get('devel_api_url', 'api.drupal.org'); - $form_state['redirect'] = "http://$api/api/function/$function/$version"; + $form_state['redirect'] = "http://$api/api/function/$function/$version"; } +/** + * Validate handler for the Switch user form. + */ function devel_switch_user_form_validate($form, &$form_state) { if (!$account = user_load_by_name($form_state['values']['username'])) { form_set_error('username', t('Username not found')); } } +/** + * Submit handler for the Switch user form. + */ function devel_switch_user_form_submit($form, &$form_state) { $path = 'devel/switch/' . $form_state['values']['username']; $form_state['redirect'] = array( @@ -955,7 +1027,7 @@ function devel_switch_user_form_submit($form, &$form_state) { 'query' => array( 'destination' => '', 'token' => drupal_get_token($path . '|'), - ))); + ))); } /** @@ -1010,24 +1082,34 @@ function devel_library_alter(&$libraries, $module) { * The bulk of the work is done in devel_shutdown_real(). */ function devel_shutdown() { - // Register the real shutdown function so it runs later than other shutdown functions. + // Register the real shutdown function so it runs after other shutdown + // functions. drupal_register_shutdown_function('devel_shutdown_real'); global $devel_run_id; - $devel_run_id = devel_xhprof_is_enabled() ? devel_shutdown_xhprof(): NULL; + $devel_run_id = devel_xhprof_is_enabled() ? devel_shutdown_xhprof() : NULL; if ($devel_run_id && function_exists('drush_log')) { drush_log('xhprof link: ' . devel_xhprof_link($devel_run_id, 'url'), 'notice'); } } +/** + * Implements hook_page_alter(). + */ function devel_page_alter($page) { if (variable_get('devel_page_alter', FALSE) && user_access('access devel information')) { dpm($page, 'page'); } } -// AJAX render reponses sometimers are sent as text/html so we have to catch them here -// and disable our footer stuff. +/** + * Implements hook_ajax_render_alter(). + * + * Disables our footer stuff based on ajax response. + * + * AJAX render reponses sometimes are sent as text/html. We have to catch them + * here and disable our footer stuff. + */ function devel_ajax_render_alter() { $GLOBALS['devel_shutdown'] = FALSE; } @@ -1050,7 +1132,8 @@ function devel_shutdown_real() { if (function_exists('drupal_get_http_header')) { $header = drupal_get_http_header('content-type'); if ($header) { - $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values'); + $formats = array('xml', 'javascript', 'json', 'plain', 'image', + 'application', 'csv', 'x-comma-separated-values'); foreach ($formats as $format) { if (strstr($header, $format)) { return; @@ -1066,14 +1149,18 @@ function devel_shutdown_real() { } if ($output) { - // TODO: gzip this text if we are sending a gzip page. see drupal_page_header(). - // For some reason, this is not actually printing for cached pages even though it gets executed - // and $output looks good. + // TODO: gzip this text if we are sending a gzip page. + // See drupal_page_header(). + // For some reason, this is not actually printing for cached pages even + // though it gets executed and $output looks good. print $output; } } } +/** + * Returns the rendered shutdown summary. + */ function devel_shutdown_summary($queries) { $sum = 0; $output = ''; @@ -1100,35 +1187,53 @@ function devel_shutdown_summary($queries) { } } +/** + * Returns the XHProf run ID. + */ function devel_shutdown_xhprof() { - $namespace = variable_get('site_name', ''); // namespace for your application + // Namespace for your application. + $namespace = variable_get('site_name', ''); $xhprof_data = xhprof_disable(); $xhprof_runs = new XHProfRuns_Default(); return $xhprof_runs->save_run($xhprof_data, $namespace); } +/** + * Returns the XHProf link. + */ function devel_xhprof_link($run_id, $type = 'link') { // @todo: render results from within Drupal. $xhprof_url = variable_get('devel_xhprof_url', ''); - $namespace = variable_get('site_name', ''); // namespace for your application + // Namespace for your application. + $namespace = variable_get('site_name', ''); if ($xhprof_url) { - $url = $xhprof_url . "/index.php?run=$run_id&source=$namespace"; + $url = $xhprof_url . '/index.php?run=' . urlencode($run_id) . '&source=' . urlencode($namespace); return $type == 'url' ? $url : t('XHProf output. ', array('@xhprof' => $url)); } } +/** + * Returns the rendered memory usage. + */ function devel_shutdown_memory() { global $memory_init; - if (variable_get('dev_mem', FALSE)) { + if (variable_get('devel_memory', FALSE)) { $memory_shutdown = memory_get_usage(); - $args = array('@memory_boot' => round($memory_init / 1024 / 1024, 2), '@memory_shutdown' => round($memory_shutdown / 1024 / 1024, 2), '@memory_peak' => round(memory_get_peak_usage(TRUE) / 1024 / 1024, 2)); + $args = array( + '@memory_boot' => round($memory_init / 1024 / 1024, 2), + '@memory_shutdown' => round($memory_shutdown / 1024 / 1024, 2), + '@memory_peak' => round(memory_get_peak_usage(TRUE) / 1024 / 1024, 2) + ); $msg = ' Memory used at: devel_boot()=@memory_boot MB, devel_shutdown()=@memory_shutdown MB, PHP peak=@memory_peak MB.'; // theme() may not be available. not t() either. return t_safe($msg, $args); } } +/** + * Returns the rendered query log. + */ function devel_shutdown_query($queries) { if (!empty($queries)) { if (function_exists('theme_get_registry') && theme_get_registry()) { @@ -1146,7 +1251,11 @@ function devel_shutdown_query($queries) { } } -// Write the variables information to the a file. It will be retrieved on demand via AJAX. +/** + * Writes the variables information to a file. + * + * It will be retrieved on demand via AJAX. + */ function devel_query_put_contents($queries) { $request_id = mt_rand(1, 1000000); $path = "temporary://devel_querylog"; @@ -1164,16 +1273,23 @@ function devel_query_put_contents($queries) { // Save queries as a json array. Suppress errors due to recursion () file_put_contents($path, @json_encode($queries)); $settings['devel'] = array( - // A random string that is sent to the browser. It enables the AJAX to retrieve queries from this request. + // A random string that is sent to the browser. + // It enables the AJAX to retrieve queries from this request. 'request_id' => $request_id, ); - print '\n"; + print '\n"; } +/** + * Returns whether query logging is enabled. + */ function devel_query_enabled() { return method_exists('Database', 'getLog') && variable_get('devel_query_display', FALSE); } +/** + * Returns the query summary. + */ function devel_query_summary($queries) { if (variable_get('devel_query_display', FALSE) && is_array($queries)) { $sum = 0; @@ -1182,12 +1298,20 @@ function devel_query_summary($queries) { $sum += $query['time']; } $counts = array_count_values($text); - return array($counts, t_safe('Executed @queries queries in @time ms.', array('@queries' => count($queries), '@time' => round($sum * 1000, 2)))); + return array( + $counts, + t_safe('Executed @queries queries in @time ms.', + array('@queries' => count($queries), '@time' => round($sum * 1000, 2))), + ); } } +/** + * Devel's t_safe() function. + */ function t_safe($string, $args) { - // get_t caused problems here with theme registry after changing on admin/build/modules. the theme_get_registry call is needed. + // get_t() caused problems here with the theme registry after changing on + // admin/build/modules. The theme_get_registry() call is needed! if (function_exists('t') && function_exists('theme_get_registry')) { theme_get_registry(); return t($string, $args); @@ -1197,11 +1321,14 @@ function t_safe($string, $args) { } } +/** + * Returns the core version. + */ function devel_get_core_version($version) { $version_parts = explode('.', $version); // Map from 4.7.10 -> 4.7 if ($version_parts[0] < 5) { - return $version_parts[0] .'.'. $version_parts[1]; + return $version_parts[0] . '.' . $version_parts[1]; } // Map from 5.5 -> 5 or 6.0-beta2 -> 6 else { @@ -1209,31 +1336,34 @@ function devel_get_core_version($version) { } } -// See http://drupal.org/node/126098 +/** + * Returns whether the optimizer is compatible. + */ function devel_is_compatible_optimizer() { - ob_start(); - phpinfo(); - $info = ob_get_contents(); - ob_end_clean(); + // See http://drupal.org/node/126098. + ob_start(); + phpinfo(); + $info = ob_get_contents(); + ob_end_clean(); - // Match the Zend Optimizer version in the phpinfo information - $found = preg_match('/Zend Optimizer v([0-9])\.([0-9])\.([0-9])/', $info, $matches); + // Match the Zend Optimizer version in the phpinfo information. + $found = preg_match('/Zend Optimizer v([0-9])\.([0-9])\.([0-9])/', $info, $matches); - if ($matches) { - $major = $matches[1]; - $minor = $matches[2]; - $build = $matches[3]; + if ($matches) { + $major = $matches[1]; + $minor = $matches[2]; + $build = $matches[3]; - if ($major >= 3) { - if ($minor >= 3) { - return TRUE; - } - elseif ($minor == 2 && $build >= 8) { - return TRUE; - } - else { - return FALSE; - } + if ($major >= 3) { + if ($minor >= 3) { + return TRUE; + } + elseif ($minor == 2 && $build >= 8) { + return TRUE; + } + else { + return FALSE; + } } else { return FALSE; @@ -1278,7 +1408,7 @@ function devel_execute_form() { } /** - * Process PHP execute form submissions. + * Processes PHP execute form submissions. */ function devel_execute_form_submit($form, &$form_state) { ob_start(); @@ -1288,10 +1418,13 @@ function devel_execute_form_submit($form, &$form_state) { } /** - * Switch from original user to another user and back. - * We don't call session_save_session() because we really want to change users. Usually unsafe! + * Switches to a different user. * - * @param $name The username to switch to, or NULL to log out. + * We don't call session_save_session() because we really want to change users. + * Usually unsafe! + * + * @param string $name + * The username to switch to, or NULL to log out. */ function devel_switch_user($name = NULL) { global $user; @@ -1317,18 +1450,20 @@ function devel_switch_user($name = NULL) { } /** - * Print an object or array using either Krumo (if installed) or devel_print_object() + * Prints an object using either Krumo (if installed) or devel_print_object(). * - * @param $object - * array or object to print - * @param $prefix - * prefixing for output items + * @param array|object $object + * An array or object to print. + * @param string $prefix + * Prefix for output items. */ function kdevel_print_object($object, $prefix = NULL) { return has_krumo() ? krumo_ob($object) : devel_print_object($object, $prefix); } -// Save krumo htlm using output buffering. +/** + * Saves krumo html using output buffering. + */ function krumo_ob($object) { ob_start(); krumo($object); @@ -1338,20 +1473,24 @@ function krumo_ob($object) { } /** - * Display an object or array + * Displays an object or array. * - * @param $object - * the object or array - * @param $prefix - * prefix for the output items (example "$node->", "$user->", "$") - * @param $header - * set to FALSE to suppress the output of the h3 + * @param array|object $object + * The object or array to display. + * @param string $prefix + * Prefix for the output items (example "$node->", "$user->", "$"). + * @param boolean $header + * Set to FALSE to suppress the output of the h3 tag. */ function devel_print_object($object, $prefix = NULL, $header = TRUE) { - drupal_add_css(drupal_get_path('module', 'devel') .'/devel.css'); + drupal_add_css(drupal_get_path('module', 'devel') . '/devel.css'); $output = '
    '; if ($header) { - $output .= '

    '. t('Display of !type !obj', array('!type' => str_replace(array('$', '->'), '', $prefix), '!obj' => gettype($object))) .'

    '; + $output .= '

    ' . t('Display of !type !obj', array( + '!type' => str_replace(array('$', '->'), '', $prefix), + '!obj' => gettype($object), + ) + ) . '

    '; } $output .= _devel_print_object($object, $prefix); $output .= '
    '; @@ -1359,19 +1498,22 @@ function devel_print_object($object, $prefix = NULL, $header = TRUE) { } /** - * Recursive (and therefore magical) function goes through an array or object and - * returns a nicely formatted listing of its contents. + * Returns formatted listing for an array or object. * - * @param $obj - * array or object to recurse through - * @param $prefix - * prefix for the output items (example "$node->", "$user->", "$") - * @param $parents - * used by recursion - * @param $object - * used by recursion - * @return - * fomatted html + * Recursive (and therefore magical) function goes through an array or object + * and returns a nicely formatted listing of its contents. + * + * @param array|object $obj + * Array or object to recurse through. + * @param string $prefix + * Prefix for the output items (example "$node->", "$user->", "$"). + * @param string $parents + * Used by recursion. + * @param boolean $object + * Used by recursion. + * + * @return string + * Formatted html. * * @todo * currently there are problems sending an array with a varname @@ -1392,24 +1534,24 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA } if (is_object($obj)) { - $obj = (array)$obj; + $obj = (array) $obj; } if (is_array($obj)) { $output = "
    \n"; foreach ($obj as $field => $value) { - if ($field == 'devel_flag_reference') { + if ($field === 'devel_flag_reference') { continue; } if (!is_null($parents)) { if ($object) { - $field = $parents .'->'. $field; + $field = $parents . '->' . $field; } else { if (is_int($field)) { - $field = $parents .'['. $field .']'; + $field = $parents . '[' . $field . ']'; } else { - $field = $parents .'[\''. $field .'\']'; + $field = $parents . '[\'' . $field . '\']'; } } } @@ -1420,9 +1562,9 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA $summary = NULL; if ($show_summary) { switch ($type) { - case 'string' : - case 'float' : - case 'integer' : + case 'string': + case 'float': + case 'integer': if (strlen($value) == 0) { $summary = t("{empty}"); } @@ -1433,20 +1575,20 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA $summary = format_plural(drupal_strlen($value), '1 character', '@count characters'); } break; - case 'array' : - case 'object' : - $summary = format_plural(count((array)$value), '1 element', '@count elements'); + case 'array': + case 'object': + $summary = format_plural(count((array) $value), '1 element', '@count elements'); break; - case 'boolean' : + case 'boolean': $summary = $value ? t('TRUE') : t('FALSE'); break; } } if (!is_null($summary)) { - $typesum = '('. $type .', '. $summary .')'; + $typesum = '(' . $type . ', ' . $summary . ')'; } else { - $typesum = '('. $type .')'; + $typesum = '(' . $type . ')'; } $output .= ''; @@ -1463,11 +1605,11 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA } elseif (is_object($value)) { $value->devel_flag_reference = FALSE; - $output .= _devel_print_object((array)$value, $prefix, $field, TRUE); + $output .= _devel_print_object((array) $value, $prefix, $field, TRUE); } else { $value = is_bool($value) ? ($value ? 'TRUE' : 'FALSE') : $value; - $output .= htmlspecialchars(print_r($value, TRUE)) ."\n"; + $output .= htmlspecialchars(print_r($value, TRUE)) . "\n"; } $output .= "\n"; } @@ -1477,12 +1619,14 @@ function _devel_print_object($obj, $prefix = NULL, $parents = NULL, $object = FA } /** - * Adds a table at the bottom of the page cataloguing data on all the database queries that were made to - * generate the page. + * Shows all the queries for the page. + * + * Adds a table at the bottom of the page cataloguing data on all the database + * queries that were made to generate the page. */ function devel_query_table($queries, $counts) { $version = devel_get_core_version(VERSION); - $header = array ('ms', '#', 'where', 'ops', 'query', 'target'); + $header = array('ms', '#', 'where', 'ops', 'query', 'target'); $i = 0; $api = variable_get('devel_api_url', 'api.drupal.org'); foreach ($queries as $query) { @@ -1492,18 +1636,33 @@ function devel_query_table($queries, $counts) { $diff = round($query['time'] * 1000, 2); if ($diff > variable_get('devel_execution', 5)) { - $cell[$i][] = array ('data' => $diff, 'class' => 'marker'); + $cell[$i][] = array('data' => $diff, 'class' => 'marker'); } else { $cell[$i][] = $diff; } $cell[$i][] = $count; $cell[$i][] = l($function, "http://$api/api/function/$function/$version"); - $ops[] = l('P', '', array('attributes' => array('title' => 'Show placeholders', 'class' => 'dev-placeholders', 'qid' => $i))); - $ops[] = l('A', '', array('attributes' => array('title' => 'Show arguments', 'class' => 'dev-arguments', 'qid' => $i))); + $ops[] = l(t('P'), '', array( + 'attributes' => array( + 'title' => 'Show placeholders', + 'class' => array('dev-placeholders'), + 'qid' => $i, + ))); + $ops[] = l(t('A'), '', array( + 'attributes' => array( + 'title' => 'Show arguments', + 'class' => array('dev-arguments'), + 'qid' => $i, + ))); // EXPLAIN only valid for select queries. if (strpos($query['query'], 'UPDATE') === FALSE && strpos($query['query'], 'INSERT') === FALSE && strpos($query['query'], 'DELETE') === FALSE) { - $ops[] = l('E', '', array('attributes' => array('title' => 'Show EXPLAIN', 'class' => 'dev-explain', 'qid' => $i))); + $ops[] = l(t('E'), '', array( + 'attributes' => array( + 'title' => 'Show EXPLAIN', + 'class' => array('dev-explain'), + 'qid' => $i, + ))); } $cell[$i][] = implode(' ', $ops); // 3 divs for each variation of the query. Last 2 are hidden by default. @@ -1524,6 +1683,9 @@ function devel_query_table($queries, $counts) { return theme('devel_querylog', array('header' => $header, 'rows' => $cell)); } +/** + * Themes devel's querylog row. + */ function theme_devel_querylog_row($variables) { $row = $variables['row']; $i = 0; @@ -1554,6 +1716,9 @@ function theme_devel_querylog_row($variables) { return $output; } +/** + * Themes devel's querylog. + */ function theme_devel_querylog($variables) { $header = $variables['header']; $rows = $variables['rows']; @@ -1576,6 +1741,9 @@ function theme_devel_querylog($variables) { return $output; } +/** + * Devel's table sort. + */ function _devel_table_sort($a, $b) { $a = is_array($a[0]) ? $a[0]['data'] : $a[0]; $b = is_array($b[0]) ? $b[0]['data'] : $b[0]; @@ -1596,7 +1764,9 @@ function devel_timer() { return t_safe(' Page execution time was @time ms.', array('@time' => $time)); } -// An alias for drupal_debug(). +/** + * An alias for drupal_debug(). + */ function dd($data, $label = NULL) { return drupal_debug($data, $label); } @@ -1604,14 +1774,13 @@ function dd($data, $label = NULL) { /** * Logs a variable to a drupal_debug.txt in the site's temp directory. * - * @param $data + * @param mixed $data * The variable to log to the drupal_debug.txt log file. - * @param $label + * @param string $label * (optional) If set, a label to output before $data in the log file. * - * @return - * No return value if successful, FALSE if the log file could not be written - * to. + * @return void|false + * Empty if successful, FALSE if the log file could not be written. * * @see dd() * @see http://drupal.org/node/314112 @@ -1620,15 +1789,15 @@ function drupal_debug($data, $label = NULL) { $out = ($label ? $label . ': ' : '') . print_r($data, TRUE) . "\n"; // The temp directory does vary across multiple simpletest instances. - $file = 'temporary://drupal_debug.txt'; + $file = file_directory_temp() . '/drupal_debug.txt'; if (file_put_contents($file, $out, FILE_APPEND) === FALSE) { - drupal_set_message(t('The file could not be written.'), 'error'); + drupal_set_message(t('Devel was unable to write to %file.', array('%file' => $file)), 'error'); return FALSE; } } /** - * Prints the arguments passed into the current function + * Prints the arguments passed into the current function. */ function dargs($always = TRUE) { static $printed; @@ -1640,27 +1809,29 @@ function dargs($always = TRUE) { } /** - * Prints a SQL string from a DBTNG SelectQuery object. + * Prints a SQL string from a DBTNG Select object. Includes quoted arguments. * * Includes quoted arguments. * - * @param $query + * @param object $query * An object that implements the SelectQueryInterface interface. - * @param $return + * @param string $return * Whether to return the string. Default is FALSE, meaning to print it * and return $query instead. - * @param $name + * @param string $name * Optional name for identifying the output. - * @return + * @return object|string * The $query object, or the query string if $return was TRUE. */ function dpq($query, $return = FALSE, $name = NULL) { if (user_access('access devel information')) { - $query->preExecute(); + if (method_exists($query, 'preExecute')) { + $query->preExecute(); + } $sql = (string) $query; $quoted = array(); $connection = Database::getConnection(); - foreach ((array)$query->arguments() as $key => $val) { + foreach ((array) $query->arguments() as $key => $val) { $quoted[$key] = $connection->quote($val); } $sql = strtr($sql, $quoted); @@ -1673,17 +1844,18 @@ function dpq($query, $return = FALSE, $name = NULL) { } /** - * Print a variable to the 'message' area of the page. + * Prints a variable to the 'message' area of the page. * * Uses drupal_set_message(). * * @param $input * An arbitrary value to output. - * @param $name + * @param string $name * Optional name for identifying the output. - * @param $type + * @param string $type * Optional message type for drupal_set_message(), defaults to 'status'. - * @return + * + * @return input * The unaltered input value. */ function dpm($input, $name = NULL, $type = 'status') { @@ -1695,15 +1867,16 @@ function dpm($input, $name = NULL, $type = 'status') { } /** - * drupal_var_export() a variable to the 'message' area of the page. + * Displays a drupal_var_export() variable to the 'message' area of the page. * * Uses drupal_set_message(). * * @param $input * An arbitrary value to output. - * @param $name + * @param string $name * Optional name for identifying the output. - * @return + * + * @return input * The unaltered input value. */ function dvm($input, $name = NULL) { @@ -1714,7 +1887,11 @@ function dvm($input, $name = NULL) { return $input; } -// legacy function that was poorly named. use dpm() instead, since the 'p' maps to 'print_r' +/** + * Legacy function that was poorly named. + * + * Use dpm() instead, since the 'p' maps to 'print_r'. + */ function dsm($input, $name = NULL) { return dpm($input, $name); } @@ -1734,17 +1911,20 @@ function kpr($input, $return = FALSE, $name = NULL) { } /** - * Like dpr, but uses drupal_var_export() instead + * Like dpr(), but uses drupal_var_export() instead. */ function dvr($input, $return = FALSE, $name = NULL) { return dprint_r($input, $return, $name, 'drupal_var_export', FALSE); } +/** + * Krumo print. + */ function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') { - // We do not want to krumo() strings and integers and such + // We do not want to krumo() strings and integers and such. if (merits_krumo($input)) { if (user_access('access devel information')) { - return $return ? (isset($name) ? $name .' => ' : '') . krumo_ob($input) : krumo($input); + return $return ? (isset($name) ? $name . ' => ' : '') . krumo_ob($input) : krumo($input); } } else { @@ -1754,6 +1934,7 @@ function kprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r') /** * Pretty-print a variable to the browser (no krumo). + * * Displays only for users with proper permissions. If * you want a string returned instead of a print, use the 2nd param. */ @@ -1776,11 +1957,11 @@ function dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $output = check_plain($output); } if (count($input, COUNT_RECURSIVE) > DEVEL_MIN_TEXTAREA) { - // don't use fapi here because sometimes fapi will not be loaded - $printed_value = "'; + // Don't use fapi here because sometimes fapi will not be loaded. + $printed_value = "'; } else { - $printed_value = '
    '. $name . $output .'
    '; + $printed_value = '
    ' . $name . $output . '
    '; } if ($return) { @@ -1809,11 +1990,26 @@ function devel_render() { } /** - * Print the function call stack. + * Prints the function call stack. + * + * @param $return + * Pass TRUE to return the formatted backtrace rather than displaying it in + * the browser via kprint_r(). + * @param $pop + * How many items to pop from the top of the stack; useful when calling from + * an error handler. + * @param $options + * Options to pass on to PHP's debug_backtrace(), depending on your PHP + * version. + * + * @return string|NULL + * The formatted backtrace, if requested, or NULL. + * + * @see http://php.net/manual/en/function.debug-backtrace.php */ -function ddebug_backtrace($return = FALSE, $pop = 0) { +function ddebug_backtrace($return = FALSE, $pop = 0, $options = TRUE) { if (user_access('access devel information')) { - $backtrace = debug_backtrace(); + $backtrace = debug_backtrace($options); while ($pop-- > 0) { array_shift($backtrace); } @@ -1847,6 +2043,7 @@ function ddebug_backtrace($return = FALSE, $pop = 0) { else { $function = $backtrace[1]['function'] . '()'; } + $backtrace[1] += array('args' => array()); $call['args'] = $backtrace[1]['args']; } else { @@ -1864,31 +2061,33 @@ function ddebug_backtrace($return = FALSE, $pop = 0) { } } -// Delete all files in a dir. +/** + * Deletes all files in a dir. + */ function devel_empty_dir($dir) { - foreach (new DirectoryIterator($dir) as $fileInfo) { - if ($fileInfo->isFile()) { - unlink($fileInfo->getPathname()); + foreach (new DirectoryIterator($dir) as $file_info) { + if ($file_info->isFile()) { + unlink($file_info->getPathname()); } } } /* - * migration related functions + * Migration-related functions. */ /** - * Regenerate the data in node_comment_statistics table. Technique comes from - * http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101 + * Regenerates the data in node_comment_statistics table. + * Technique - http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101 * * @return void - **/ + */ function devel_rebuild_node_comment_statistics() { - // Empty table + // Empty table. db_truncate('node_comment_statistics')->execute(); - // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case when - // two comments on the same node share same timestamp. + // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case + // when two comments on the same node share same timestamp. $sql = " INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) ( SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c @@ -1908,7 +2107,7 @@ function devel_rebuild_node_comment_statistics() { $query->addExpression('NULL', 'last_comment_name'); $query->isNull('ncs.comment_count'); - db_insert('node_comment_statistics') + db_insert('node_comment_statistics', array('return' => Database::RETURN_NULL)) ->from($query) ->execute(); } @@ -1935,8 +2134,7 @@ function devel_form_user_admin_permissions_alter(&$form, &$form_state) { /** * Implements hook_form_alter(). * - * Adds mouse-over hints on the Modules page to display - * module base names. + * Adds mouse-over hints on the Modules page to display module base names. */ function devel_form_system_modules_alter(&$form, &$form_state) { if (user_access('access devel information') && variable_get('devel_raw_names', FALSE) && isset($form['modules']) && is_array($form['modules'])) { diff --git a/sites/all/modules/contrib/dev/devel/devel.pages.inc b/sites/all/modules/contrib/dev/devel/devel.pages.inc index 9870f520..7a137da3 100644 --- a/sites/all/modules/contrib/dev/devel/devel.pages.inc +++ b/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'), diff --git a/sites/all/modules/contrib/dev/devel/devel.test b/sites/all/modules/contrib/dev/devel/devel.test index 7d66bf7f..50defcf6 100644 --- a/sites/all/modules/contrib/dev/devel/devel.test +++ b/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'); } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.drush.inc b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.drush.inc index f90a57ad..65ae3822 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.drush.inc +++ b/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 diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.inc index 7e757b38..072dafe1 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.inc +++ b/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); } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info index 877b618a..b45159f5 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.info +++ b/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" diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module index 3776c222..11bc2916 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.module +++ b/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 = '. ' . t('Comments: ') . $map[$default]. ''; + $options[$type->type]['comments'] = ''. $map[$default]. ''; } - $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)))); } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test new file mode 100644 index 00000000..c649f024 --- /dev/null +++ b/sites/all/modules/contrib/dev/devel/devel_generate/devel_generate.test @@ -0,0 +1,108 @@ + 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')); + } +} diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc index 84ed5edf..c4e76eac 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/file.devel_generate.inc +++ b/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); diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc index df1705ce..df4ff588 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/image.devel_generate.inc +++ b/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; } diff --git a/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc b/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc index 1f4691d2..c63a92ed 100644 --- a/sites/all/modules/contrib/dev/devel/devel_generate/text.devel_generate.inc +++ b/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; diff --git a/sites/all/modules/contrib/dev/devel/devel_krumo_path.js b/sites/all/modules/contrib/dev/devel/devel_krumo_path.js index fa01f771..d4a578dd 100644 --- a/sites/all/modules/contrib/dev/devel/devel_krumo_path.js +++ b/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(''); + $('.krumo-footnote .krumo-call').once().before(''); var krumo_name = []; var krumo_type = []; diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access.info b/sites/all/modules/contrib/dev/devel/devel_node_access.info index 07195ccb..22b82bcf 100644 --- a/sites/all/modules/contrib/dev/devel/devel_node_access.info +++ b/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" diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access.js b/sites/all/modules/contrib/dev/devel/devel_node_access.js index 60f782cf..b7909429 100644 --- a/sites/all/modules/contrib/dev/devel/devel_node_access.js +++ b/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); \ No newline at end of file +})(jQuery); diff --git a/sites/all/modules/contrib/dev/devel/devel_node_access.module b/sites/all/modules/contrib/dev/devel/devel_node_access.module index 8cde0222..74e927e9 100644 --- a/sites/all/modules/contrib/dev/devel/devel_node_access.module +++ b/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' => '' . $row['nid'] . '', 'title' => $grant['#title'], ); - $row['realm'] = (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0 ? '' : $grant['#module'] . ':
    ') . $grant['realm']; + if (empty($grant['#module']) || strpos($grant['realm'], $grant['#module']) === 0) { + $row['realm'] = $grant['realm']; + } + else { + $row['realm'] = array( + 'data' => $grant['#module'] . ':
    ' . $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'] = '
    ' . _devel_node_access_get_grant_list($nid, $ng_alter_data) . '
    '; + $variables['!list'] = '
    ' . _devel_node_access_get_grant_list($nid, $ng_alter_datas['view']) . '
    '; $variables['%access'] = 'view'; $output[] = array( '#prefix' => "\n
    ", @@ -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' => '
    ', ); } - $variables['!username'] = theme('username', array('account' => $account)); + $variables['!username'] = '' . theme('username', array('account' => $account)) . ''; $output[] = array( '#prefix' => "\n
    ", - '#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
    \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'), ' $node_type->name)) . '">' . t('create') . '', t('view'), t('update'), t('delete')); + $variables = array('@Node_type' => ($node_type ? $node_type->name : $node->type)); + $create_header = '' . t('create') . ''; + if (!$node_type) { + $create_header .= '
    ' . t("(missing type: '@Node_type')", $variables) . ''; + } + $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)) { diff --git a/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php b/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php index 70da0b9d..25dd1e53 100644 --- a/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php +++ b/sites/all/modules/contrib/dev/devel/krumo/class.krumo.php @@ -604,7 +604,8 @@ This is a list of all the values from the $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)) { diff --git a/sites/all/modules/contrib/dev/devel/runtests.sh b/sites/all/modules/contrib/dev/devel/runtests.sh new file mode 100644 index 00000000..4eb200a1 --- /dev/null +++ b/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 diff --git a/sites/all/modules/contrib/dev/libraries/CHANGELOG.txt b/sites/all/modules/contrib/dev/libraries/CHANGELOG.txt index 4738559b..e3c94119 100644 --- a/sites/all/modules/contrib/dev/libraries/CHANGELOG.txt +++ b/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. - diff --git a/sites/all/modules/contrib/dev/libraries/libraries.api.php b/sites/all/modules/contrib/dev/libraries/libraries.api.php index 9ae1b32f..8ac31115 100644 --- a/sites/all/modules/contrib/dev/libraries/libraries.api.php +++ b/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. diff --git a/sites/all/modules/contrib/dev/libraries/libraries.info b/sites/all/modules/contrib/dev/libraries/libraries.info index d4b2af3f..136d323b 100644 --- a/sites/all/modules/contrib/dev/libraries/libraries.info +++ b/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" diff --git a/sites/all/modules/contrib/dev/libraries/libraries.module b/sites/all/modules/contrib/dev/libraries/libraries.module index 687fa290..0448476d 100644 --- a/sites/all/modules/contrib/dev/libraries/libraries.module +++ b/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. * diff --git a/sites/all/modules/contrib/dev/libraries/tests/example/example_1.css b/sites/all/modules/contrib/dev/libraries/tests/example/example_1.css deleted file mode 100644 index a732bda5..00000000 --- a/sites/all/modules/contrib/dev/libraries/tests/example/example_1.css +++ /dev/null @@ -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; -} diff --git a/sites/all/modules/contrib/dev/libraries/tests/example/example_2.css b/sites/all/modules/contrib/dev/libraries/tests/example/example_2.css deleted file mode 100644 index c8f92899..00000000 --- a/sites/all/modules/contrib/dev/libraries/tests/example/example_2.css +++ /dev/null @@ -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; -} diff --git a/sites/all/modules/contrib/dev/libraries/tests/example/example_3.css b/sites/all/modules/contrib/dev/libraries/tests/example/example_3.css deleted file mode 100644 index ffef054a..00000000 --- a/sites/all/modules/contrib/dev/libraries/tests/example/example_3.css +++ /dev/null @@ -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; -} diff --git a/sites/all/modules/contrib/dev/libraries/tests/example/example_4.css b/sites/all/modules/contrib/dev/libraries/tests/example/example_4.css deleted file mode 100644 index 5030a614..00000000 --- a/sites/all/modules/contrib/dev/libraries/tests/example/example_4.css +++ /dev/null @@ -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; -} diff --git a/sites/all/modules/contrib/dev/libraries/tests/libraries.test b/sites/all/modules/contrib/dev/libraries/tests/libraries.test index 1770162e..41926318 100644 --- a/sites/all/modules/contrib/dev/libraries/tests/libraries.test +++ b/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:
    ' . var_export($library, TRUE) . '
    '); $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:
    ' . var_export($library, TRUE) . '
    '); $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:
    ' . var_export($info['example_module'], TRUE) . '
    '); + $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:
    ' . var_export($info['example_theme'], TRUE) . '
    '); + $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('
    ' . var_export($loaded, TRUE) . '
    '); $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 info callback group was invoked.', 'Info callback invoked for uncached libraries.'); $this->assertRaw('The pre-detect callback group was invoked.', 'Pre-detect callback invoked for uncached libraries.'); $this->assertRaw('The post-detect callback group was invoked.', 'Post-detect callback invoked for uncached libraries.'); @@ -462,13 +498,13 @@ class LibrariesTestCase extends DrupalWebTestCase { $this->assertRaw('The post-load 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 info callback group was not invoked.', 'Info callback not invoked for cached libraries.'); $this->assertNoRaw('The pre-detect callback group was not invoked.', 'Pre-detect callback not invoked for cached libraries.'); $this->assertNoRaw('The post-detect callback group was not invoked.', 'Post-detect callback not invoked for cached libraries.'); $this->assertRaw('The pre-load callback group was invoked.', 'Pre-load callback invoked for cached libraries.'); $this->assertRaw('The post-load 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'))) { diff --git a/sites/all/modules/contrib/dev/libraries/tests/example/README.txt b/sites/all/modules/contrib/dev/libraries/tests/libraries/example/README.txt similarity index 76% rename from sites/all/modules/contrib/dev/libraries/tests/example/README.txt rename to sites/all/modules/contrib/dev/libraries/tests/libraries/example/README.txt index e3582c2a..6c50a58a 100644 --- a/sites/all/modules/contrib/dev/libraries/tests/example/README.txt +++ b/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). diff --git a/sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.css b/sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.css new file mode 100644 index 00000000..ed9ea3c2 --- /dev/null +++ b/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; +} diff --git a/sites/all/modules/contrib/dev/libraries/tests/example/example_1.js b/sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.js similarity index 52% rename from sites/all/modules/contrib/dev/libraries/tests/example/example_1.js rename to sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.js index 8a1b9a27..659ff0ff 100644 --- a/sites/all/modules/contrib/dev/libraries/tests/example/example_1.js +++ b/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.') } }; diff --git a/sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.php b/sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.php new file mode 100644 index 00000000..4142e411 --- /dev/null +++ b/sites/all/modules/contrib/dev/libraries/tests/libraries/example/example_1.php @@ -0,0 +1,15 @@ + 'Example module', + 'module_altered' => FALSE, + ); + // Test library detection. $libraries['example_missing'] = array( 'name' => 'Example missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/missing', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/missing', ); $libraries['example_undetected_version'] = array( 'name' => 'Example undetected version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', - 'version callback' => '_libraries_test_return_version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', + 'version callback' => '_libraries_test_module_return_version', 'version arguments' => array(FALSE), ); $libraries['example_unsupported_version'] = array( 'name' => 'Example unsupported version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', - 'version callback' => '_libraries_test_return_version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', + 'version callback' => '_libraries_test_module_return_version', 'version arguments' => array('1'), 'versions' => array( '2' => array(), ), ); - $libraries['example_supported_version'] = array( 'name' => 'Example supported version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', - 'version callback' => '_libraries_test_return_version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', + 'version callback' => '_libraries_test_module_return_version', 'version arguments' => array('1'), 'versions' => array( '1' => array(), @@ -43,7 +48,7 @@ function libraries_test_libraries_info() { // Test the default version callback. $libraries['example_default_version_callback'] = array( 'name' => 'Example default version callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version arguments' => array( 'file' => 'README.txt', // Version 1 @@ -55,16 +60,16 @@ function libraries_test_libraries_info() { // Test a multiple-parameter version callback. $libraries['example_multiple_parameter_version_callback'] = array( 'name' => 'Example multiple parameter version callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', // Version 1 - 'version callback' => '_libraries_test_get_version', + 'version callback' => '_libraries_test_module_get_version', 'version arguments' => array('README.txt', '/Version (\d+)/', 5), ); // Test a top-level files property. $libraries['example_files'] = 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'), @@ -76,23 +81,41 @@ function libraries_test_libraries_info() { // Test loading of integration files. // Normally added by the corresponding module via hook_libraries_info_alter(), // these files should be automatically loaded when the library is loaded. - $libraries['example_integration_files'] = array( - 'name' => 'Example integration files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + $libraries['example_module_integration_files'] = array( + 'name' => 'Example module integration files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1', 'integration files' => array( - 'libraries_test' => array( - 'js' => array('libraries_test.js'), - 'css' => array('libraries_test.css'), - 'php' => array('libraries_test.inc'), + 'libraries_test_module' => array( + 'js' => array('libraries_test_module.js'), + 'css' => array('libraries_test_module.css'), + 'php' => array('libraries_test_module.inc'), ), ), ); + // Test loading of integration files after library files. + // We test the correct loading order by calling a function that is defined in + // example_1.php in libraries_test_module_post_load.inc. + $libraries['example_module_integration_files_post_load'] = array( + 'name' => 'Example module post-load integration files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', + 'version' => '1', + 'files' => array( + 'php' => array('example_1.php'), + ), + 'integration files' => array( + 'libraries_test_module' => array( + 'php' => array('libraries_test_module_post_load.inc'), + ), + ), + 'post-load integration files' => TRUE, + ); + // Test version overloading. $libraries['example_versions'] = array( 'name' => 'Example versions', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '2', 'versions' => array( '1' => array( @@ -115,7 +138,7 @@ function libraries_test_libraries_info() { // Test variant detection. $libraries['example_variant_missing'] = array( 'name' => 'Example variant missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1', 'variants' => array( 'example_variant' => array( @@ -124,7 +147,7 @@ function libraries_test_libraries_info() { 'css' => array('example_3.css'), 'php' => array('example_3.php'), ), - 'variant callback' => '_libraries_test_return_installed', + 'variant callback' => '_libraries_test_module_return_installed', 'variant arguments' => array(FALSE), ), ), @@ -132,7 +155,7 @@ function libraries_test_libraries_info() { $libraries['example_variant'] = array( 'name' => 'Example variant', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1', 'variants' => array( 'example_variant' => array( @@ -141,7 +164,7 @@ function libraries_test_libraries_info() { 'css' => array('example_3.css'), 'php' => array('example_3.php'), ), - 'variant callback' => '_libraries_test_return_installed', + 'variant callback' => '_libraries_test_module_return_installed', 'variant arguments' => array(TRUE), ), ), @@ -150,7 +173,7 @@ function libraries_test_libraries_info() { // Test correct behaviour with multiple versions and multiple variants. $libraries['example_versions_and_variants'] = array( 'name' => 'Example versions and variants', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '2', 'versions' => array( '1' => array( @@ -161,7 +184,7 @@ function libraries_test_libraries_info() { 'css' => array('example_1.css'), 'php' => array('example_1.php'), ), - 'variant callback' => '_libraries_test_return_installed', + 'variant callback' => '_libraries_test_module_return_installed', 'variant arguments' => array(TRUE), ), 'example_variant_2' => array( @@ -170,7 +193,7 @@ function libraries_test_libraries_info() { 'css' => array('example_2.css'), 'php' => array('example_2.php'), ), - 'variant callback' => '_libraries_test_return_installed', + 'variant callback' => '_libraries_test_module_return_installed', 'variant arguments' => array(TRUE), ), ), @@ -183,7 +206,7 @@ function libraries_test_libraries_info() { 'css' => array('example_3.css'), 'php' => array('example_3.php'), ), - 'variant callback' => '_libraries_test_return_installed', + 'variant callback' => '_libraries_test_module_return_installed', 'variant arguments' => array(TRUE), ), 'example_variant_2' => array( @@ -192,7 +215,7 @@ function libraries_test_libraries_info() { 'css' => array('example_4.css'), 'php' => array('example_4.php'), ), - 'variant callback' => '_libraries_test_return_installed', + 'variant callback' => '_libraries_test_module_return_installed', 'variant arguments' => array(TRUE), ), ), @@ -206,27 +229,27 @@ function libraries_test_libraries_info() { // This library acts as a dependency for the libraries below. $libraries['example_dependency'] = array( 'name' => 'Example dependency', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1.1', 'files' => array('js' => array('example_1.js')), ); $libraries['example_dependency_missing'] = array( 'name' => 'Example dependency missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1', 'dependencies' => array('example_missing'), 'files' => array('js' => array('example_1.js')), ); $libraries['example_dependency_incompatible'] = array( 'name' => 'Example dependency incompatible', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1', 'dependencies' => array('example_dependency (>1.1)'), 'files' => array('js' => array('example_1.js')), ); $libraries['example_dependency_compatible'] = array( 'name' => 'Example dependency compatible', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', 'version' => '1', 'dependencies' => array('example_dependency (>=1.1)'), 'files' => array('js' => array('example_1.js')), @@ -235,7 +258,7 @@ function libraries_test_libraries_info() { // Test the applying of callbacks. $libraries['example_callback'] = 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( @@ -271,12 +294,12 @@ function libraries_test_libraries_info() { ), ), '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'), ), // These keys are for testing purposes only. 'info callback' => 'not applied', @@ -287,24 +310,40 @@ function libraries_test_libraries_info() { 'post-load callback' => 'not applied', ); + $libraries['example_path_variable_override'] = array( + 'name' => 'Example path variable override', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', + 'version' => '1', + 'files' => array( + 'php' => array('example_1.php', 'example_2.php'), + ), + ); + return $libraries; } +/** + * Implements hook_libraries_info_alter(). + */ +function libraries_test_module_libraries_info_alter(&$libraries) { + $libraries['example_module']['module_altered'] = TRUE; +} + /** * Implements hook_libraries_info_file_paths() */ -function libraries_test_libraries_info_file_paths() { - return array(drupal_get_path('module', 'libraries_test') . '/example'); +function libraries_test_module_libraries_info_file_paths() { + return array(drupal_get_path('module', 'libraries') . '/tests/libraries'); } /** * Gets the version of an example library. * * Returns exactly the version string entered as the $version parameter. This - * function cannot be collapsed with _libraries_test_return_installed(), because - * of the different arguments that are passed automatically. + * function cannot be collapsed with _libraries_test_module_return_installed(), + * because of the different arguments that are passed automatically. */ -function _libraries_test_return_version($library, $version) { +function _libraries_test_module_return_version($library, $version) { return $version; } @@ -340,7 +379,7 @@ function _libraries_test_return_version($library, $version) { * * @see libraries_get_version() */ -function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $cols = 200) { +function _libraries_test_module_get_version($library, $file, $pattern, $lines = 20, $cols = 200) { $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file; if (!file_exists($file)) { @@ -361,10 +400,10 @@ function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $co * Detects the variant of an example library. * * Returns exactly the value of $installed, either TRUE or FALSE. This function - * cannot be collapsed with _libraries_test_return_version(), because of the - * different arguments that are passed automatically. + * cannot be collapsed with _libraries_test_module_return_version(), because of + * the different arguments that are passed automatically. */ -function _libraries_test_return_installed($library, $name, $installed) { +function _libraries_test_module_return_installed($library, $name, $installed) { return $installed; } @@ -373,10 +412,10 @@ function _libraries_test_return_installed($library, $name, $installed) { * * This function is used as a test callback for the 'info' callback group. * - * @see _libraries_test_callback() + * @see _libraries_test_module_callback() */ -function _libraries_test_info_callback(&$library, $version, $variant) { - _libraries_test_callback($library, $version, $variant, 'info'); +function _libraries_test_module_info_callback(&$library, $version, $variant) { + _libraries_test_module_callback($library, $version, $variant, 'info'); } /** @@ -384,10 +423,10 @@ function _libraries_test_info_callback(&$library, $version, $variant) { * * This function is used as a test callback for the 'pre-detect' callback group. * - * @see _libraries_test_callback() + * @see _libraries_test_module_callback() */ -function _libraries_test_pre_detect_callback(&$library, $version, $variant) { - _libraries_test_callback($library, $version, $variant, 'pre-detect'); +function _libraries_test_module_pre_detect_callback(&$library, $version, $variant) { + _libraries_test_module_callback($library, $version, $variant, 'pre-detect'); } /** @@ -395,10 +434,10 @@ function _libraries_test_pre_detect_callback(&$library, $version, $variant) { * * This function is used as a test callback for the 'post-detect callback group. * - * @see _libraries_test_callback() + * @see _libraries_test_module_callback() */ -function _libraries_test_post_detect_callback(&$library, $version, $variant) { - _libraries_test_callback($library, $version, $variant, 'post-detect'); +function _libraries_test_module_post_detect_callback(&$library, $version, $variant) { + _libraries_test_module_callback($library, $version, $variant, 'post-detect'); } /** @@ -407,10 +446,10 @@ function _libraries_test_post_detect_callback(&$library, $version, $variant) { * This function is used as a test callback for the 'pre-dependencies-load' * callback group. * - * @see _libraries_test_callback() + * @see _libraries_test_module_callback() */ -function _libraries_test_pre_dependencies_load_callback(&$library, $version, $variant) { - _libraries_test_callback($library, $version, $variant, 'pre-dependencies-load'); +function _libraries_test_module_pre_dependencies_load_callback(&$library, $version, $variant) { + _libraries_test_module_callback($library, $version, $variant, 'pre-dependencies-load'); } /** @@ -418,10 +457,10 @@ function _libraries_test_pre_dependencies_load_callback(&$library, $version, $va * * This function is used as a test callback for the 'pre-load' callback group. * - * @see _libraries_test_callback() + * @see _libraries_test_module_callback() */ -function _libraries_test_pre_load_callback(&$library, $version, $variant) { - _libraries_test_callback($library, $version, $variant, 'pre-load'); +function _libraries_test_module_pre_load_callback(&$library, $version, $variant) { + _libraries_test_module_callback($library, $version, $variant, 'pre-load'); } /** @@ -429,10 +468,10 @@ function _libraries_test_pre_load_callback(&$library, $version, $variant) { * * This function is used as a test callback for the 'post-load' callback group. * - * @see _libraries_test_callback() + * @see _libraries_test_module_callback() */ -function _libraries_test_post_load_callback(&$library, $version, $variant) { - _libraries_test_callback($library, $version, $variant, 'post-load'); +function _libraries_test_module_post_load_callback(&$library, $version, $variant) { + _libraries_test_module_callback($library, $version, $variant, 'post-load'); } /** @@ -456,7 +495,7 @@ function _libraries_test_post_load_callback(&$library, $version, $variant) { * The variant the library information passed in $library belongs to, or NULL * if the passed library information is not variant-specific. */ -function _libraries_test_callback(&$library, $version, $variant, $group) { +function _libraries_test_module_callback(&$library, $version, $variant, $group) { $string = 'applied'; if (isset($version) && isset($variant)) { $string .= " (version $version, variant $variant)"; @@ -475,7 +514,7 @@ function _libraries_test_callback(&$library, $version, $variant, $group) { // The following is used to test caching of library information. // Only set the message for the top-level library to prevent confusing, // duplicate messages. - if (!isset($version) && !isset($variant) && variable_get('libraries_test_cache', FALSE)) { + if (!isset($version) && !isset($variant) && variable_get('libraries_test_module_cache', FALSE)) { drupal_set_message("The $group callback group was invoked."); } } @@ -483,32 +522,40 @@ function _libraries_test_callback(&$library, $version, $variant, $group) { /** * Implements hook_menu(). */ -function libraries_test_menu() { +function libraries_test_module_menu() { $base = array( - 'page callback' => '_libraries_test_load', + 'page callback' => '_libraries_test_module_load', 'access callback' => TRUE, ); - $items['libraries_test/files'] = $base + array( + $items['libraries-test-module/files'] = $base + array( 'title' => 'Test files', 'page arguments' => array('example_files'), ); - $items['libraries_test/integration_files'] = $base + array( - 'title' => 'Test integration files', - 'page arguments' => array('example_integration_files'), + $items['libraries-test-module/module-integration-files'] = $base + array( + 'title' => 'Test module integration files', + 'page arguments' => array('example_module_integration_files'), ); - $items['libraries_test/versions'] = $base + array( + $items['libraries-test-module/module-integration-files-post-load'] = $base + array( + 'title' => 'Test module post-load integration files', + 'page arguments' => array('example_module_integration_files_post_load'), + ); + $items['libraries-test-module/theme-integration-files'] = $base + array( + 'title' => 'Test theme integration files', + 'page arguments' => array('example_theme_integration_files'), + ); + $items['libraries-test-module/versions'] = $base + array( 'title' => 'Test version loading', 'page arguments' => array('example_versions'), ); - $items['libraries_test/variant'] = $base + array( + $items['libraries-test-module/variant'] = $base + array( 'title' => 'Test variant loading', 'page arguments' => array('example_variant', 'example_variant'), ); - $items['libraries_test/versions_and_variants'] = $base + array( + $items['libraries-test-module/versions-and-variants'] = $base + array( 'title' => 'Test concurrent version and variant loading', 'page arguments' => array('example_versions_and_variants', 'example_variant_2'), ); - $items['libraries_test/cache'] = $base + array( + $items['libraries-test-module/cache'] = $base + array( 'title' => 'Test caching of library information', 'page arguments' => array('example_callback'), ); @@ -523,7 +570,7 @@ function libraries_test_menu() { * JavaScript and CSS files for easier debugging. See example/README.txt for * more information. */ -function _libraries_test_load($library, $variant = NULL) { +function _libraries_test_module_load($library, $variant = NULL) { libraries_load($library, $variant); // JavaScript and CSS files can be checked directly by SimpleTest, so we only // need to manually check for PHP files. @@ -532,14 +579,14 @@ function _libraries_test_load($library, $variant = NULL) { // For easer debugging of JS loading, a text is shown that the JavaScript will // replace. $output .= '

    JavaScript

    '; - $output .= '
    '; + $output .= '
    '; $output .= 'If this text shows up, no JavaScript test file was loaded.'; $output .= '
    '; // For easier debugging of CSS loading, the loaded CSS files will color the // following text. $output .= '

    CSS

    '; - $output .= '
    '; + $output .= '
    '; $output .= 'If one of the CSS test files has been loaded, this text will be colored:'; $output .= '
      '; // Do not reference the actual CSS files (i.e. including '.css'), because that @@ -548,17 +595,18 @@ function _libraries_test_load($library, $variant = NULL) { $output .= '
    • example_2: green
    • '; $output .= '
    • example_3: orange
    • '; $output .= '
    • example_4: blue
    • '; - $output .= '
    • libraries_test: purple
    • '; + $output .= '
    • libraries_test_module: purple
    • '; + $output .= '
    • libraries_test_theme: turquoise
    • '; $output .= '
    '; $output .= '
    '; $output .= '

    PHP

    '; - $output .= '
    '; + $output .= '
    '; $output .= 'The following is a list of all loaded test PHP files:'; $output .= '
      '; $files = get_included_files(); foreach ($files as $file) { - if (strpos($file, 'libraries/test') && !strpos($file, 'libraries_test.module')) { + if (strpos($file, 'libraries/test') && !strpos($file, 'libraries_test_module.module') && !strpos($file, 'template.php')) { $output .= '
    • ' . str_replace(DRUPAL_ROOT . '/', '', $file) . '
    • '; } } @@ -567,3 +615,12 @@ function _libraries_test_load($library, $variant = NULL) { return $output; } + +/** + * Implements hook_system_theme_info(). + */ +function libraries_test_module_system_theme_info() { + $themes = array(); + $themes['libraries_test_theme'] = drupal_get_path('module', 'libraries') . '/tests/themes/libraries_test_theme/libraries_test_theme.info'; + return $themes; +} diff --git a/sites/all/modules/contrib/dev/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc b/sites/all/modules/contrib/dev/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc new file mode 100644 index 00000000..8e308f25 --- /dev/null +++ b/sites/all/modules/contrib/dev/libraries/tests/modules/libraries_test_module/libraries_test_module_post_load.inc @@ -0,0 +1,15 @@ + 'Example theme', + 'theme_altered' => FALSE, + ); + $libraries['example_theme_integration_files'] = array( + 'name' => 'Example theme integration file', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/libraries/example', + 'version' => '1', + 'integration files' => array( + 'libraries_test_theme' => array( + 'js' => array('libraries_test_theme.js'), + 'css' => array('libraries_test_theme.css'), + 'php' => array('libraries_test_theme.inc'), + ), + ), + ); + return $libraries; +} + +/** + * Implements hook_libraries_info_alter(). + */ +function libraries_test_theme_libraries_info_alter(&$libraries) { + $libraries['example_theme']['theme_altered'] = TRUE; +} diff --git a/sites/all/modules/contrib/dev/performance/README.txt b/sites/all/modules/contrib/dev/performance/README.txt index 6fa0775b..4b43ae47 100644 --- a/sites/all/modules/contrib/dev/performance/README.txt +++ b/sites/all/modules/contrib/dev/performance/README.txt @@ -98,6 +98,33 @@ Statistics: You can view the recorded performance statistics (summary and details) at /admin/reports/performance-logging +Custom detailed logging implementation +-------------------------------------- +As mentioned before, detailed logging is NOT recommended on production environ- +ments. If you, for whatever reason, DO wish detailed logging on production, you +should create a custom detailed logging mechanism that will NOT interfere with +your live site. You can do this by creating your own versions of the following +functions: + + - performance_log_details($params) + => function that is called to store the performance data + - performance_view_details() + => function that is called to view the stored detail log. This function is + called from hook_menu() and should return content that Drupal can render + as a page. + - performance_clear_details() + => function that is called to delete the entire detail log + +Have a look at includes/performance.details.inc for more details about these +functions. + +When you have created those functions, add the location of the file containing +your custom implementation to settings.php like so: + + $conf['performance_detail_logging'] = './sites/all/path/to/your/file'; + +NOTE: there is NO drush support for your custom detail logging implementation! + Drush support ------------- Drush support has been integrated as well. You can check the summary and detail diff --git a/sites/all/modules/contrib/dev/performance/includes/performance.details.inc b/sites/all/modules/contrib/dev/performance/includes/performance.details.inc new file mode 100644 index 00000000..0c92ec2d --- /dev/null +++ b/sites/all/modules/contrib/dev/performance/includes/performance.details.inc @@ -0,0 +1,137 @@ + REQUEST_TIME, + 'bytes' => $params['mem'], + 'ms' => (int)$params['timer'], + 'query_count' => $params['query_count'], + 'query_timer' => (int)$params['query_timer'], + 'anon' => $params['anon'], + 'path' => $params['path'], + 'language' => $params['language'], + 'data' => $params['data'], + ); + + try { + db_insert('performance_detail') + ->fields($fields) + ->execute(); + } + catch (Exception $e) { + watchdog_exception('performance', $e, NULL, array(), WATCHDOG_ERROR); + } +} + +/** + * Detail page callback. + * @return array + * Drupal render array. + */ +function performance_view_details() { + drupal_set_title(t('Performance logs: Details')); + + $header = array( + array('data' => t('#'), 'field' => 'pid', 'sort' => 'desc'), + array('data' => t('Path'), 'field' => 'path'), + array('data' => t('Date'), 'field' => 'timestamp'), + array('data' => t('Memory (MB)'), 'field' => 'bytes'), + array('data' => t('ms (Total)'), 'field' => 'ms'), + array('data' => t('Language'), 'field' => 'language'), + array('data' => t('Anonymous?'), 'field' => 'anon'), + ); + if (variable_get(PERFORMANCE_QUERY_VAR, 0)) { + $header[] = array('data' => t('# Queries'), 'field' => 'query_count'); + $header[] = array('data' => t('Query ms'), 'field' => 'query_timer'); + } + + $pager_height = 50; + $result = db_select('performance_detail', 'p') + ->fields('p') + ->extend('PagerDefault') + ->limit($pager_height) + ->extend('TableSort') + ->orderByHeader($header) + ->execute(); + + $rows = array(); + + foreach ($result as $data) { + $row_data = array(); + $row_data[] = $data->pid; + $row_data[] = l($data->path, $data->path); + $row_data[] = format_date($data->timestamp, 'small'); + $row_data[] = number_format($data->bytes / 1024 / 1024, 2); + $row_data[] = $data->ms; + $row_data[] = $data->language; + $row_data[] = ($data->anon) ? t('Yes') : t('No'); + + if (variable_get(PERFORMANCE_QUERY_VAR, 0)) { + $row_data[] = $data->query_count; + $row_data[] = $data->query_timer; + } + + $rows[] = array('data' => $row_data); + } + + if (empty($rows) && !variable_get('performance_detail', 0)) { + return array( + 'content' => array( + '#markup' => t('Detail performance log is not enabled. Go to the !link to enable it.', array('!link' => l(t('settings page'), PERFORMANCE_SETTINGS, array('query' => drupal_get_destination())))) + ), + ); + } + elseif (!variable_get('performance_detail', 0)) { + drupal_set_message(t('Detail performance log is not enabled! Showing stored logs.'), 'warning'); + } + + // Return a renderable array. + return array( + 'query_data_detail' => array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#sticky' => TRUE, + '#empty' => t('No log messages available.'), + ), + 'clear' => array( + '#markup' => l(t('Clear logs'), 'admin/reports/performance-logging/clear/details'), + ), + 'pager' => array( + '#theme' => 'pager', + '#quantity' => $pager_height, + ), + ); +} + +/** + * Helper function to clear the detail logs. + * @return void + */ +function performance_clear_details() { + db_truncate('performance_detail')->execute(); +} + +/** + * Helper function to prune detail log on cron. + * @return void + */ +function performance_prune_details() { + db_delete('performance_detail') + ->condition('timestamp', REQUEST_TIME - (24 * 60 * 60), '<=') + ->execute(); +} diff --git a/sites/all/modules/contrib/dev/performance/performance.info b/sites/all/modules/contrib/dev/performance/performance.info index 51908ae0..5c481efd 100644 --- a/sites/all/modules/contrib/dev/performance/performance.info +++ b/sites/all/modules/contrib/dev/performance/performance.info @@ -6,9 +6,9 @@ configure = admin/config/development/performance-logging tags[] = developer tags[] = monitoring -; Information added by drupal.org packaging script on 2013-01-12 -version = "7.x-2.0-beta1" +; Information added by packaging script on 2013-11-09 +version = "7.x-2.0" core = "7.x" project = "performance" -datestamp = "1358019516" +datestamp = "1384025307" diff --git a/sites/all/modules/contrib/dev/performance/performance.module b/sites/all/modules/contrib/dev/performance/performance.module index eee5c1bb..7129aa8d 100644 --- a/sites/all/modules/contrib/dev/performance/performance.module +++ b/sites/all/modules/contrib/dev/performance/performance.module @@ -23,6 +23,8 @@ define('PERFORMANCE_CACHE', 'cache_default_class'); define('PERFORMANCE_SETTINGS', 'admin/config/development/performance-logging'); +include_once variable_get('performance_detail_logging', 'includes/performance.details.inc'); + /** * Implements hook_menu(). */ @@ -101,9 +103,9 @@ function performance_cron() { performance_traverse_cache('performance_cron_prune'); // Remove performance_detail rows on a daily basis. - db_delete('performance_detail') - ->condition('timestamp', REQUEST_TIME - (24 * 60 * 60), '<=') - ->execute(); + if (variable_get('performance_detail', 0)) { + performance_prune_details(); + } } /** @@ -345,7 +347,7 @@ function performance_log_summary($params) { } // Keep records for 1 day. - $expire = $result['last_access'] + (24 * 60 * 60); + $expire = $result['data']['last_access'] + (24 * 60 * 60); cache_set($key, $result['data'], PERFORMANCE_BIN, $expire); } @@ -481,32 +483,6 @@ function performance_get_summary($cache, $timestamp) { return; } -/** - * Helper function to store detailed data in database. - */ -function performance_log_details($params = array()) { - $fields = array( - 'timestamp' => REQUEST_TIME, - 'bytes' => $params['mem'], - 'ms' => (int)$params['timer'], - 'query_count' => $params['query_count'], - 'query_timer' => (int)$params['query_timer'], - 'anon' => $params['anon'], - 'path' => $params['path'], - 'language' => $params['language'], - 'data' => $params['data'], - ); - - try { - db_insert('performance_detail') - ->fields($fields) - ->execute(); - } - catch (Exception $e) { - watchdog_exception('performance', $e, NULL, array(), WATCHDOG_ERROR); - } -} - /** * Summary page callback. */ @@ -688,85 +664,6 @@ function performance_sort_summary($data, $direction, $field) { return $data; } -/** - * Detail page callback. - */ -function performance_view_details() { - drupal_set_title(t('Performance logs: Details')); - - $header = array( - array('data' => t('#'), 'field' => 'pid', 'sort' => 'desc'), - array('data' => t('Path'), 'field' => 'path'), - array('data' => t('Date'), 'field' => 'timestamp'), - array('data' => t('Memory (MB)'), 'field' => 'bytes'), - array('data' => t('ms (Total)'), 'field' => 'ms'), - array('data' => t('Language'), 'field' => 'language'), - array('data' => t('Anonymous?'), 'field' => 'anon'), - ); - if (variable_get(PERFORMANCE_QUERY_VAR, 0)) { - $header[] = array('data' => t('# Queries'), 'field' => 'query_count'); - $header[] = array('data' => t('Query ms'), 'field' => 'query_timer'); - } - - $pager_height = 50; - $result = db_select('performance_detail', 'p') - ->fields('p') - ->extend('PagerDefault') - ->limit($pager_height) - ->extend('TableSort') - ->orderByHeader($header) - ->execute(); - - $rows = array(); - - foreach ($result as $data) { - $row_data = array(); - $row_data[] = $data->pid; - $row_data[] = l($data->path, $data->path); - $row_data[] = format_date($data->timestamp, 'small'); - $row_data[] = number_format($data->bytes / 1024 / 1024, 2); - $row_data[] = $data->ms; - $row_data[] = $data->language; - $row_data[] = ($data->anon) ? t('Yes') : t('No'); - - if (variable_get(PERFORMANCE_QUERY_VAR, 0)) { - $row_data[] = $data->query_count; - $row_data[] = $data->query_timer; - } - - $rows[] = array('data' => $row_data); - } - - if (empty($rows) && !variable_get('performance_detail', 0)) { - return array( - 'content' => array( - '#markup' => t('Detail performance log is not enabled. Go to the !link to enable it.', array('!link' => l(t('settings page'), PERFORMANCE_SETTINGS, array('query' => drupal_get_destination())))) - ), - ); - } - elseif (!variable_get('performance_detail', 0)) { - drupal_set_message(t('Detail performance log is not enabled! Showing stored logs.'), 'warning'); - } - - // Return a renderable array. - return array( - 'query_data_detail' => array( - '#theme' => 'table', - '#header' => $header, - '#rows' => $rows, - '#sticky' => TRUE, - '#empty' => t('No log messages available.'), - ), - 'clear' => array( - '#markup' => l(t('Clear logs'), 'admin/reports/performance-logging/clear/details'), - ), - 'pager' => array( - '#theme' => 'pager', - '#quantity' => $pager_height, - ), - ); -} - /** * Clear logs form. */ @@ -812,7 +709,7 @@ function performance_clear_form_submit($form, &$form_state) { cache_clear_all('*', PERFORMANCE_BIN, TRUE); break; case 'details': - db_truncate('performance_detail')->execute(); + performance_clear_details(); break; } diff --git a/sites/all/modules/contrib/editor/pathologic/pathologic.api.php b/sites/all/modules/contrib/editor/pathologic/pathologic.api.php index fce2e63a..09760f9f 100644 --- a/sites/all/modules/contrib/editor/pathologic/pathologic.api.php +++ b/sites/all/modules/contrib/editor/pathologic/pathologic.api.php @@ -72,7 +72,7 @@ function hook_pathologic_alter(&$url_params, $parts, $settings) { } // If it's a path to a local image, make sure it's using our CDN server. - if (preg_match('~\.(png|gif|jpe?g)$', $url_params['path'])) { + if (preg_match('~\.(png|gif|jpe?g)$~', $url_params['path'])) { $url_params['path'] = 'http://cdn.example.com/' . $url_params['path']; $url_params['options']['external'] = TRUE; } diff --git a/sites/all/modules/contrib/editor/pathologic/pathologic.info b/sites/all/modules/contrib/editor/pathologic/pathologic.info index c4950943..0016e52c 100644 --- a/sites/all/modules/contrib/editor/pathologic/pathologic.info +++ b/sites/all/modules/contrib/editor/pathologic/pathologic.info @@ -5,9 +5,9 @@ dependencies[] = filter core = 7.x files[] = pathologic.test -; Information added by drupal.org packaging script on 2013-07-09 -version = "7.x-2.11" +; Information added by Drupal.org packaging script on 2013-12-14 +version = "7.x-2.12" core = "7.x" project = "pathologic" -datestamp = "1373385363" +datestamp = "1387055607" diff --git a/sites/all/modules/contrib/editor/pathologic/pathologic.module b/sites/all/modules/contrib/editor/pathologic/pathologic.module index 52a8ec95..806fd3f1 100644 --- a/sites/all/modules/contrib/editor/pathologic/pathologic.module +++ b/sites/all/modules/contrib/editor/pathologic/pathologic.module @@ -75,6 +75,16 @@ function _pathologic_settings($form, &$form_state, $filter, $format, $defaults, * if language path prefixing (eg /ja/node/123) is in use. REMEMBER THIS IN THE * FUTURE, ALBRIGHT. * + * The below code uses the @ operator before parse_url() calls because in PHP + * 5.3.2 and earlier, parse_url() causes a warning of parsing fails. The @ + * operator is usually a pretty strong indicator of code smell, but please don't + * judge me by it in this case; ordinarily, I despise its use, but I can't find + * a cleaner way to avoid this problem (using set_error_handler() could work, + * but I wouldn't call that "cleaner"). Fortunately, Drupal 8 will require at + * least PHP 5.3.5, so this mess doesn't have to spread into the D8 branch of + * Pathologic. + * @see https://drupal.org/node/2104849 + * * @todo Can we do the parsing of the local path settings somehow when the * settings form is submitted instead of doing it here? */ @@ -82,14 +92,14 @@ function _pathologic_filter($text, $filter, $format, $langcode, $cache, $cache_i // Get the base URL and explode it into component parts. We add these parts // to the exploded local paths settings later. global $base_url; - $base_url_parts = parse_url($base_url . '/'); + $base_url_parts = @parse_url($base_url . '/'); // Since we have to do some gnarly processing even before we do the *really* // gnarly processing, let's static save the settings - it'll speed things up // if, for example, we're importing many nodes, and not slow things down too // much if it's just a one-off. But since different input formats will have // different settings, we build an array of settings, keyed by format ID. - $settings = &drupal_static(__FUNCTION__, array()); - if (!isset($settings[$filter->format])) { + $cached_settings = &drupal_static(__FUNCTION__, array()); + if (!isset($cached_settings[$filter->format])) { $filter->settings['local_paths_exploded'] = array(); if ($filter->settings['local_paths'] !== '') { // Build an array of the exploded local paths for this format's settings. @@ -98,7 +108,7 @@ function _pathologic_filter($text, $filter, $format, $langcode, $cache, $cache_i // @see http://drupal.org/node/1727492 $local_paths = array_filter(array_map('trim', explode("\n", $filter->settings['local_paths']))); foreach ($local_paths as $local) { - $parts = parse_url($local); + $parts = @parse_url($local); // Okay, what the hellish "if" statement is doing below is checking to // make sure we aren't about to add a path to our array of exploded // local paths which matches the current "local" path. We consider it @@ -145,37 +155,39 @@ function _pathologic_filter($text, $filter, $format, $langcode, $cache, $cache_i // We'll also just store the host part separately for easy access. $filter->settings['base_url_host'] = $base_url_parts['host']; - $settings[$filter->format] = $filter->settings; + $cached_settings[$filter->format] = $filter->settings; } // Get the language code for the text we're about to process. - $settings['langcode'] = $langcode; + $cached_settings['langcode'] = $langcode; // And also take note of which settings in the settings array should apply. - $settings['current_settings'] = &$settings[$filter->format]; + $cached_settings['current_settings'] = &$cached_settings[$filter->format]; // Now that we have all of our settings prepared, attempt to process all // paths in href, src, action or longdesc HTML attributes. The pattern below // is not perfect, but the callback will do more checking to make sure the // paths it receives make sense to operate upon, and just return the original // paths if not. - return preg_replace_callback('~(href|src|action|longdesc)="([^"]+)~i', '_pathologic_replace', $text); + return preg_replace_callback('~ (href|src|action|longdesc)="([^"]+)~i', '_pathologic_replace', $text); } /** * Process and replace paths. preg_replace_callback() callback. */ function _pathologic_replace($matches) { + // Get the base path. + global $base_path; + // Get the settings for the filter. Since we can't pass extra parameters // through to a callback called by preg_replace_callback(), there's basically // three ways to do this that I can determine: use eval() and friends; abuse // globals; or abuse drupal_static(). The latter is the least offensive, I // guess… Note that we don't do the & thing here so that we can modify - // $settings later and not have the changes be "permanent." - $settings = drupal_static('_pathologic_filter'); + // $cached_settings later and not have the changes be "permanent." + $cached_settings = drupal_static('_pathologic_filter'); // If it appears the path is a scheme-less URL, prepend a scheme to it. // parse_url() cannot properly parse scheme-less URLs. Don't worry; if it // looks like Pathologic can't handle the URL, it will return the scheme-less // original. - // @see https://drupal.org/node/1617944 // @see https://drupal.org/node/2030789 if (strpos($matches[2], '//') === 0) { @@ -190,7 +202,7 @@ function _pathologic_replace($matches) { // @see http://drupal.org/node/1672932 $original_url = htmlspecialchars_decode($matches[2]); // …and parse the URL - $parts = parse_url($original_url); + $parts = @parse_url($original_url); // Do some more early tests to see if we should just give up now. if ( // If parse_url() failed, give up. @@ -227,7 +239,7 @@ function _pathologic_replace($matches) { if (isset($parts['scheme']) && $parts['scheme'] === 'files') { // Path Filter "files:" support. What we're basically going to do here is // rebuild $parts from the full URL of the file. - $new_parts = parse_url(file_create_url(file_default_scheme() . '://' . $parts['path'])); + $new_parts = @parse_url(file_create_url(file_default_scheme() . '://' . $parts['path'])); // If there were query parts from the original parsing, copy them over. if (!empty($parts['query'])) { $new_parts['query'] = $parts['query']; @@ -235,10 +247,10 @@ function _pathologic_replace($matches) { $new_parts['path'] = rawurldecode($new_parts['path']); $parts = $new_parts; // Don't do language handling for file paths. - $settings['is_file'] = TRUE; + $cached_settings['is_file'] = TRUE; } else { - $settings['is_file'] = FALSE; + $cached_settings['is_file'] = FALSE; } // Let's also bail out of this doesn't look like a local path. @@ -246,7 +258,7 @@ function _pathologic_replace($matches) { // Cycle through local paths and find one with a host and a path that matches; // or just a host if that's all we have; or just a starting path if that's // what we have. - foreach ($settings['current_settings']['local_paths_exploded'] as $exploded) { + foreach ($cached_settings['current_settings']['local_paths_exploded'] as $exploded) { // If a path is available in both… if (isset($exploded['path']) && isset($parts['path']) // And the paths match… @@ -283,7 +295,7 @@ function _pathologic_replace($matches) { // e.g.: if our url is /foo/bar we'll mark this as a match for // http://example.com but want to keep searching and would prefer a match // to http://example.com/foo if that's configured as a local path - elseif (!isset($parts['host']) && (!isset($exploded['path']) || $exploded['path'] == '/')) { + elseif (!isset($parts['host']) && (!isset($exploded['path']) || $exploded['path'] === $base_path)) { $found = TRUE; } } @@ -326,12 +338,12 @@ function _pathologic_replace($matches) { // If we didn't previously identify this as a file, check to see if the file // exists now that we have the correct path relative to DRUPAL_ROOT - if (!$settings['is_file']){ - $settings['is_file'] = !empty($parts['path']) && is_file(DRUPAL_ROOT . '/'. $parts['path']); + if (!$cached_settings['is_file']) { + $cached_settings['is_file'] = !empty($parts['path']) && is_file(DRUPAL_ROOT . '/'. $parts['path']); } // Okay, deal with language stuff. - if ($settings['is_file']) { + if ($cached_settings['is_file']) { // If we're linking to a file, use a fake LANGUAGE_NONE language object. // Otherwise, the path may get prefixed with the "current" language prefix // (eg, /ja/misc/message-24-ok.png) @@ -365,7 +377,7 @@ function _pathologic_replace($matches) { 'fragment' => isset($parts['fragment']) ? $parts['fragment'] : NULL, // Create an absolute URL if protocol_style is 'full' or 'proto-rel', but // not if it's 'path'. - 'absolute' => $settings['current_settings']['protocol_style'] !== 'path', + 'absolute' => $cached_settings['current_settings']['protocol_style'] !== 'path', // If we seem to have found a language for the path, pass it along to // url(). Otherwise, ignore the 'language' parameter. 'language' => isset($parts['language_obj']) ? $parts['language_obj'] : NULL, @@ -381,7 +393,7 @@ function _pathologic_replace($matches) { // Now alter! // @see http://drupal.org/node/1762022 - drupal_alter('pathologic', $url_params, $parts, $settings); + drupal_alter('pathologic', $url_params, $parts, $cached_settings); // If any of the alter hooks asked us to just pass along the original URL, // then do so. @@ -396,9 +408,9 @@ function _pathologic_replace($matches) { // @see http://drupal.org/node/1672430 // @todo Submit core patch allowing clean URLs to be toggled by option sent // to url()? - if (!empty($settings['is_file'])) { - $settings['orig_clean_url'] = !empty($GLOBALS['conf']['clean_url']); - if (!$settings['orig_clean_url']) { + if (!empty($cached_settings['is_file'])) { + $cached_settings['orig_clean_url'] = !empty($GLOBALS['conf']['clean_url']); + if (!$cached_settings['orig_clean_url']) { $GLOBALS['conf']['clean_url'] = TRUE; } } @@ -408,20 +420,20 @@ function _pathologic_replace($matches) { // If we turned clean URLs on before to create a path to a file, turn them // back off. - if ($settings['is_file'] && !$settings['orig_clean_url']) { + if ($cached_settings['is_file'] && !$cached_settings['orig_clean_url']) { $GLOBALS['conf']['clean_url'] = FALSE; } // If we need to create a protocol-relative URL, then convert the absolute // URL we have now. - if ($settings['current_settings']['protocol_style'] === 'proto-rel') { + if ($cached_settings['current_settings']['protocol_style'] === 'proto-rel') { // Now, what might have happened here is that url() returned a URL which // isn't on "this" server due to a hook_url_outbound_alter() implementation. // We don't want to convert the URL in that case. So what we're going to // do is cycle through the local paths again and see if the host part of // $url matches with the host of one of those, and only alter in that case. - $url_parts = parse_url($url); - if (!empty($url_parts['host']) && $url_parts['host'] === $settings['current_settings']['base_url_host']) { + $url_parts = @parse_url($url); + if (!empty($url_parts['host']) && $url_parts['host'] === $cached_settings['current_settings']['base_url_host']) { $url = _pathologic_url_to_protocol_relative($url); } } @@ -430,7 +442,7 @@ function _pathologic_replace($matches) { // @see http://drupal.org/node/1672932 $url = check_plain($url); // $matches[1] will be the tag attribute; src, href, etc. - return "{$matches[1]}=\"{$url}"; + return " {$matches[1]}=\"{$url}"; } /** diff --git a/sites/all/modules/contrib/fields/email/email.diff.inc b/sites/all/modules/contrib/fields/email/email.diff.inc new file mode 100644 index 00000000..b9a54ff8 --- /dev/null +++ b/sites/all/modules/contrib/fields/email/email.diff.inc @@ -0,0 +1,17 @@ + $item) { + $diff_items[$delta] = $item['email']; + } + return $diff_items; +} diff --git a/sites/all/modules/contrib/fields/email/email.info b/sites/all/modules/contrib/fields/email/email.info index 3bfb2dca..a4dd1d2b 100644 --- a/sites/all/modules/contrib/fields/email/email.info +++ b/sites/all/modules/contrib/fields/email/email.info @@ -5,9 +5,9 @@ package = Fields files[] = email.migrate.inc -; Information added by drupal.org packaging script on 2012-08-29 -version = "7.x-1.2" +; Information added by Drupal.org packaging script on 2014-04-10 +version = "7.x-1.3" core = "7.x" project = "email" -datestamp = "1346254131" +datestamp = "1397134155" diff --git a/sites/all/modules/contrib/fields/email/email.migrate.inc b/sites/all/modules/contrib/fields/email/email.migrate.inc index 1b6272a2..b068acbe 100644 --- a/sites/all/modules/contrib/fields/email/email.migrate.inc +++ b/sites/all/modules/contrib/fields/email/email.migrate.inc @@ -10,7 +10,7 @@ class MigrateEmailFieldHandler extends MigrateFieldHandler { $this->registerTypes(array('email')); } - public function prepare(stdClass $entity, array $field_info, array $instance, array $values) { + public function prepare($entity, array $field_info, array $instance, array $values) { // Setup the Field API array for saving. $arguments = (isset($values['arguments'])) ? $values['arguments']: array(); $language = $this->getFieldLanguage($entity, $field_info, $arguments); diff --git a/sites/all/modules/contrib/fields/email/email.module b/sites/all/modules/contrib/fields/email/email.module index dd92fdf7..a33d85c3 100644 --- a/sites/all/modules/contrib/fields/email/email.module +++ b/sites/all/modules/contrib/fields/email/email.module @@ -1,5 +1,9 @@ 2); + return array( + 'api' => 2, + 'field handlers' => array('MigrateEmailFieldHandler'), + ); } /** @@ -198,7 +205,7 @@ function email_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('email_admin_settings'), 'access arguments' => array('administer site configuration'), - 'type' => MENU_CALLBACK, + 'type' => MENU_NORMAL_ITEM, ); return $items; } @@ -433,7 +440,7 @@ function email_mail_page_form_submit($form, &$form_state) { // Log the operation: flood_register_event('email'); - watchdog('mail', t('%name-from sent an e-mail at %form.', array('%name-from' => $form_state['values']['name'], '%form' => url($_GET['q'], array('absolute' => TRUE))))); + watchdog('mail', '%name-from sent an e-mail at %form.', array('%name-from' => $form_state['values']['name'], '%form' => url($_GET['q'], array('absolute' => TRUE)))); drupal_set_message(t('Your message has been sent.')); $form_state['redirect'] = $path; @@ -447,7 +454,7 @@ function email_mail($key, &$message, $params) { switch ($key) { case 'contact': // Compose the body: - $message['body'][] = t('@name sent a message using the contact form at @url.', array('@name' => $params['name'], '@url' => $params['url']), array('langcode' =>$language->language)); + $message['body'][] = t('@name sent a message using the contact form at @url.', array('@name' => $params['name'], '@url' => $params['url']), array('langcode' => $language->language)); $message['body'][] = $params['message']; $message['subject'] = ""; diff --git a/sites/all/modules/contrib/fields/link/link-rtl.css b/sites/all/modules/contrib/fields/link/link-rtl.css new file mode 100644 index 00000000..0359487e --- /dev/null +++ b/sites/all/modules/contrib/fields/link/link-rtl.css @@ -0,0 +1,8 @@ +.link-field-column { + float: right; +} + +.link-field-column.link-field-url .form-text { + direction: ltr; + text-align: left; +} diff --git a/sites/all/modules/contrib/fields/link/link.devel_generate.inc b/sites/all/modules/contrib/fields/link/link.devel_generate.inc index 7be4a0dd..af0e2d5d 100644 --- a/sites/all/modules/contrib/fields/link/link.devel_generate.inc +++ b/sites/all/modules/contrib/fields/link/link.devel_generate.inc @@ -21,9 +21,12 @@ function link_devel_generate($object, $field, $instance, $bundle) { * Callback for hook_devel_generate(). */ function _link_devel_generate($object, $field, $instance, $bundle) { - return array( + $link = array( 'url' => url('', array('absolute' => TRUE)), - 'title' => devel_create_greeking(mt_rand(1, 3), TRUE), 'attributes' => _link_default_attributes(), ); + if ($instance['settings']['title'] != 'none') { + $link['title'] = devel_create_greeking(mt_rand(1, 3), TRUE); + } + return $link; } diff --git a/sites/all/modules/contrib/fields/link/link.info b/sites/all/modules/contrib/fields/link/link.info index 57aedb39..016b7990 100644 --- a/sites/all/modules/contrib/fields/link/link.info +++ b/sites/all/modules/contrib/fields/link/link.info @@ -18,9 +18,9 @@ files[] = tests/link.validate.test files[] = views/link_views_handler_argument_target.inc files[] = views/link_views_handler_filter_protocol.inc -; Information added by drupal.org packaging script on 2013-02-09 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2014-10-21 +version = "7.x-1.3" core = "7.x" project = "link" -datestamp = "1360444361" +datestamp = "1413924830" diff --git a/sites/all/modules/contrib/fields/link/link.migrate.inc b/sites/all/modules/contrib/fields/link/link.migrate.inc index 6388d11e..fc8363f6 100644 --- a/sites/all/modules/contrib/fields/link/link.migrate.inc +++ b/sites/all/modules/contrib/fields/link/link.migrate.inc @@ -97,7 +97,12 @@ class MigrateLinkFieldHandler extends MigrateFieldHandler { } } if (isset($arguments['attributes'])) { - $item['attributes'] = $arguments['attributes']; + if (is_array($arguments['attributes']) && isset($arguments['attributes'][$delta])) { + $item['attributes'] = $arguments['attributes'][$delta]; + } + else { + $item['attributes'] = $arguments['attributes']; + } } $item['url'] = $value; $return[$language][$delta] = $item; diff --git a/sites/all/modules/contrib/fields/link/link.module b/sites/all/modules/contrib/fields/link/link.module index c42e9466..b0c53c37 100644 --- a/sites/all/modules/contrib/fields/link/link.module +++ b/sites/all/modules/contrib/fields/link/link.module @@ -10,7 +10,7 @@ define('LINK_INTERNAL', 'internal'); define('LINK_FRONT', 'front'); define('LINK_EMAIL', 'email'); define('LINK_NEWS', 'news'); -define('LINK_DOMAINS', 'aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local'); +define('LINK_DOMAINS', 'aero|arpa|asia|biz|build|com|cat|ceo|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|post|pro|tel|travel|mobi|local|xxx'); define('LINK_TARGET_DEFAULT', 'default'); define('LINK_TARGET_NEW_WINDOW', '_blank'); @@ -46,12 +46,14 @@ function link_field_info() { 'url' => 0, 'title' => 'optional', 'title_value' => '', + 'title_label_use_field_label' => FALSE, 'title_maxlength' => 128, 'enable_tokens' => 1, 'display' => array( 'url_cutoff' => 80, ), 'validate_url' => 1, + 'absolute_url' => 1, ), 'default_widget' => 'link_field', 'default_formatter' => 'link_default', @@ -70,6 +72,13 @@ function link_field_instance_settings_form($field, $instance) { '#element_validate' => array('link_field_settings_form_validate'), ); + $form['absolute_url'] = array( + '#type' => 'checkbox', + '#title' => t('Absolute URL'), + '#default_value' => isset($instance['settings']['absolute_url']) && ($instance['settings']['absolute_url'] !== '') ? $instance['settings']['absolute_url'] : TRUE, + '#description' => t('If checked, the URL will always render as an absolute URL.'), + ); + $form['validate_url'] = array( '#type' => 'checkbox', '#title' => t('Validate URL'), @@ -107,6 +116,13 @@ function link_field_instance_settings_form($field, $instance) { '#description' => t('This title will always be used if “Static Title” is selected above.'), ); + $form['title_label_use_field_label'] = array( + '#type' => 'checkbox', + '#title' => t('Use field label as the label for the title field'), + '#default_value' => isset($instance['settings']['title_label_use_field_label']) ? $instance['settings']['title_label_use_field_label'] : FALSE, + '#description' => t('If this is checked the field label will be hidden.'), + ); + $form['title_maxlength'] = array( '#type' => 'textfield', '#title' => t('Max length of title field'), @@ -117,28 +133,22 @@ function link_field_instance_settings_form($field, $instance) { ); if (module_exists('token')) { - // Add token module replacements fields - $form['tokens'] = array( - '#type' => 'fieldset', - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#title' => t('Placeholder tokens'), - '#description' => t("The following placeholder tokens can be used in both paths and titles. When used in a path or title, they will be replaced with the appropriate values."), - ); - $entity_info = entity_get_info($instance['entity_type']); - $form['tokens']['help'] = array( - '#theme' => 'token_tree', - '#token_types' => array($entity_info['token type']), - '#global_types' => TRUE, - '#click_insert' => TRUE, - ); - + // Add token module replacements fields. $form['enable_tokens'] = array( '#type' => 'checkbox', '#title' => t('Allow user-entered tokens'), '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1, '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the entity edit form. This does not affect the field settings on this page.'), ); + + $entity_info = entity_get_info($instance['entity_type']); + $form['tokens_help'] = array( + '#theme' => 'token_tree', + '#token_types' => array($entity_info['token type']), + '#global_types' => TRUE, + '#click_insert' => TRUE, + '#dialog' => TRUE, + ); } $form['display'] = array( @@ -189,6 +199,11 @@ function link_field_instance_settings_form($field, $instance) { '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'), '#options' => $rel_remove_options, ); + $form['attributes']['configurable_class'] = array( + '#title' => t("Allow the user to enter a custom link class per link"), + '#type' => 'checkbox', + '#default_value' => empty($instance['settings']['attributes']['configurable_class']) ? '' : $instance['settings']['attributes']['configurable_class'], + ); $form['attributes']['class'] = array( '#type' => 'textfield', '#title' => t('Additional CSS Class'), @@ -258,12 +273,16 @@ function link_field_validate($entity_type, $entity, $field, $instance, $langcode $optional_field_found = FALSE; if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) { foreach ($items as $delta => $value) { - _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found); + _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found, $errors); } } if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) { - form_set_error($field['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.')); + $errors[$field['field_name']][$langcode][0][] = array( + 'error' => 'link_required', + 'message' => t('At least one title or URL must be entered.'), + 'error_element' => array('url' => FALSE, 'title' => TRUE), + ); } } @@ -320,6 +339,18 @@ function link_field_widget_form(&$form, &$form_state, $field, $instance, $langco return $element; } +/** + * Implements hook_field_widget_error(). + */ +function link_field_widget_error($element, $error, $form, &$form_state) { + if ($error['error_element']['title']) { + form_error($element['title'], $error['message']); + } + elseif ($error['error_element']['url']) { + form_error($element['url'], $error['message']); + } +} + /** * Unpacks the item attributes for use. */ @@ -341,9 +372,11 @@ function _link_load($field, $item, $instance) { /** * Prepares the item attributes and url for storage. */ -function _link_process(&$item, $delta = 0, $field, $entity) { +function _link_process(&$item, $delta, $field, $entity) { // Trim whitespace from URL. - $item['url'] = trim($item['url']); + if (!empty($item['url'])) { + $item['url'] = trim($item['url']); + } // If no attributes are set then make sure $item['attributes'] is an empty // array, so $field['attributes'] can override it. @@ -367,41 +400,61 @@ function _link_process(&$item, $delta = 0, $field, $entity) { /** * Validates that the link field has been entered properly. */ -function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found) { +function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) { if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) { // Validate the link. if (link_validate_url(trim($item['url'])) == FALSE) { - form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][url', t('The value provided for %field is not a valid URL.', array('%field' => $instance['label']))); + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'link_required', + 'message' => t('The value %value provided for %field is not a valid URL.', array( + '%value' => trim($item['url']), + '%field' => $instance['label'], + )), + 'error_element' => array('url' => TRUE, 'title' => FALSE), + ); } // Require a title for the link if necessary. if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) { - form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][title', t('Titles are required for all links.')); + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'link_required', + 'message' => t('Titles are required for all links.'), + 'error_element' => array('url' => FALSE, 'title' => TRUE), + ); } } // Require a link if we have a title. if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) { - form_set_error($field['field_name'] . '][' . $langcode . '][' . $delta . '][url', t('You cannot enter a title without a link url.')); + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'link_required', + 'message' => t('You cannot enter a title without a link url.'), + 'error_element' => array('url' => TRUE, 'title' => FALSE), + ); } // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link. - if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) { + if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' + && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) { $optional_field_found = TRUE; } - // Require entire field + // Require entire field. if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) { - form_set_error($instance['field_name'] . '][' . $langcode . '][0][title', t('At least one title or URL must be entered.')); + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'link_required', + 'message' => t('At least one title or URL must be entered.'), + 'error_element' => array('url' => FALSE, 'title' => TRUE), + ); } } /** * Clean up user-entered values for a link field according to field settings. * - * @param $item + * @param array $item * A single link item, usually containing url, title, and attributes. - * @param $delta + * @param int $delta * The delta value if this field is one of multiple fields. - * @param $field + * @param array $field * The CCK field definition. - * @param $entity + * @param object $entity * The entity containing this link. */ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { @@ -409,6 +462,9 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { if (empty($item['url']) && empty($item['title'])) { return; } + if (empty($item['html'])) { + $item['html'] = FALSE; + } // Replace URL tokens. $entity_type = $instance['entity_type']; @@ -439,19 +495,13 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { $url = link_cleanup_url($item['url']); $url_parts = _link_parse_url($url); - // We can't check_plain('') because it'll break. - if ($type != LINK_FRONT) { - $url_parts['url'] = check_plain($url_parts['url']); - } - if (!empty($url_parts['url'])) { - $item['url'] = url($url_parts['url'], - array( - 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, - 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, - 'absolute' => TRUE, - 'html' => TRUE, - ) + $item['url'] = $url_parts['url']; + $item += array( + 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, + 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, + 'absolute' => !empty($instance['settings']['absolute_url']), + 'html' => TRUE, ); } @@ -464,7 +514,7 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { array( 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL, 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL, - 'absolute' => TRUE, + 'absolute' => !empty($instance['settings']['absolute_url']), ) ); } @@ -489,7 +539,7 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { $title = ''; } - // Replace tokens. + // Replace title tokens. if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) { // Load the entity if necessary for entities in views. if (isset($entity->{$property_id})) { @@ -511,7 +561,7 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { // Unserialize attributtes array if it has not been unserialized yet. if (!is_array($item['attributes'])) { - $item['attributes'] = (array)unserialize($item['attributes']); + $item['attributes'] = (array) unserialize($item['attributes']); } // Add default attributes. @@ -560,6 +610,16 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded)); $item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u')); } + // Handle attribute classes. + if (!empty($item['attributes']['class'])) { + $classes = explode(' ', $item['attributes']['class']); + foreach ($classes as &$class) { + $class = drupal_html_class($class); + } + $item['attributes']['class'] = implode(' ', $classes); + } + unset($item['attributes']['configurable_class']); + // Remove title attribute if it's equal to link text. if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) { unset($item['attributes']['title']); @@ -568,15 +628,6 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { // Remove empty attributes. $item['attributes'] = array_filter($item['attributes']); - - // Sets title to trimmed url if one exists - // @todo: Obsolete? - /*if(!empty($item['display_url']) && empty($item['title'])) { - $item['title'] = $item['display_url']; - } - elseif(!isset($item['title'])) { - $item['title'] = $item['url']; - }*/ } /** @@ -585,7 +636,7 @@ function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) { * @param string $url * URL to parse. * - * @return Array + * @return array * Array of url pieces - only 'url', 'query', and 'fragment'. */ function _link_parse_url($url) { @@ -598,51 +649,77 @@ function _link_parse_url($url) { // Separate out the query string, if any. if (strpos($url, '?') !== FALSE) { $query = substr($url, strpos($url, '?') + 1); - parse_str($query, $query_array); - // See http://drupal.org/node/1710578 - foreach ($query_array as $key=> &$value) { - if ($value === '' && FALSE === strpos($query, $key . '=')) { - $value = NULL; - } - } - $url_parts['query'] = $query_array; + $url_parts['query'] = _link_parse_str($query); $url = substr($url, 0, strpos($url, '?')); } $url_parts['url'] = $url; return $url_parts; } +/** + * Replaces the PHP parse_str() function. + * + * Because parse_str replaces the following characters in query parameters name + * in order to maintain compability with deprecated register_globals directive: + * + * - chr(32) ( ) (space) + * - chr(46) (.) (dot) + * - chr(91) ([) (open square bracket) + * - chr(128) - chr(159) (various) + * + * @param string $query + * Query to parse. + * + * @return array + * Array of query parameters. + * + * @see http://php.net/manual/en/language.variables.external.php#81080 + */ +function _link_parse_str($query) { + $query_array = array(); + + $pairs = explode('&', $query); + foreach ($pairs as $pair) { + $name_value = explode('=', $pair, 2); + $name = urldecode($name_value[0]); + $value = isset($name_value[1]) ? urldecode($name_value[1]) : NULL; + $query_array[$name] = $value; + } + + return $query_array; +} + /** * Implements hook_theme(). */ function link_theme() { return array( 'link_formatter_link_default' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_plain' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_absolute' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_domain' => array( - 'variables' => array('element' => NULL, 'display' => NULL), + 'variables' => array('element' => NULL, 'display' => NULL, 'field' => NULL), ), 'link_formatter_link_title_plain' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_url' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_short' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_label' => array( 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_formatter_link_separate' => array( - 'variables' => array('element' => NULL), + 'variables' => array('element' => NULL, 'field' => NULL), ), 'link_field' => array( 'render element' => 'element', @@ -668,7 +745,7 @@ function theme_link_field($vars) { if (isset($element['title'])) { $output .= ''; } - $output .= ''; + $output .= ''; $output .= '
    '; if (!empty($element['attributes']['target'])) { $output .= ''; @@ -676,6 +753,10 @@ function theme_link_field($vars) { if (!empty($element['attributes']['title'])) { $output .= ''; } + if (!empty($element['attributes']['class'])) { + $output .= ''; + } + $output .= drupal_render_children($element); return $output; } @@ -723,10 +804,21 @@ function link_field_process($element, $form_state, $complete_form) { '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL, ); if ($settings['title'] !== 'none' && $settings['title'] !== 'value') { + // Figure out the label of the title field. + if (!empty($settings['title_label_use_field_label'])) { + // Use the element label as the title field label. + $title_label = $element['#title']; + // Hide the field label because there is no need for the duplicate labels. + $element['#title_display'] = 'invisible'; + } + else { + $title_label = t('Title'); + } + $element['title'] = array( '#type' => 'textfield', '#maxlength' => $settings['title_maxlength'], - '#title' => t('Title'), + '#title' => $title_label, '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])), '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE, '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL, @@ -757,6 +849,15 @@ function link_field_process($element, $form_state, $complete_form) { '#field_suffix' => '"', ); } + if (!empty($settings['attributes']['configurable_class']) && $settings['attributes']['configurable_class'] == 1) { + $element['attributes']['class'] = array( + '#type' => 'textfield', + '#title' => t('Custom link class'), + '#default_value' => isset($attributes['class']) ? $attributes['class'] : '', + '#field_prefix' => 'class = "', + '#field_suffix' => '"', + ); + } // If the title field is avaliable or there are field accepts multiple values // then allow the individual field items display the required asterisk if needed. @@ -886,14 +987,13 @@ function theme_link_formatter_link_default($vars) { if (isset($link_options['attributes']['class'])) { $link_options['attributes']['class'] = array($link_options['attributes']['class']); } - // Display a normal link if both title and URL are available. if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) { return l($vars['element']['title'], $vars['element']['url'], $link_options); } // If only a title, display the title. elseif (!empty($vars['element']['title'])) { - return check_plain($vars['element']['title']); + return $link_options['html'] ? $vars['element']['title'] : check_plain($vars['element']['title']); } elseif (!empty($vars['element']['url'])) { return l($vars['element']['title'], $vars['element']['url'], $link_options); @@ -916,7 +1016,7 @@ function theme_link_formatter_link_plain($vars) { } /** - * Formats a link as an absolute URL + * Formats a link as an absolute URL. */ function theme_link_formatter_link_absolute($vars) { $absolute = array('absolute' => TRUE); @@ -985,10 +1085,8 @@ function theme_link_formatter_link_separate($vars) { unset($link_options['url']); $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']); - /** - * @TODO static html markup looks not very elegant - * needs smarter output solution and an optional title/url seperator - */ + // @TODO static html markup looks not very elegant + // needs smarter output solution and an optional title/url seperator $url_parts = _link_parse_url($vars['element']['url']); $output = ''; $output .= '